You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rave.apache.org by ja...@apache.org on 2011/08/17 17:54:08 UTC

svn commit: r1158787 - in /incubator/rave/trunk/rave-portal/src: main/java/org/apache/rave/portal/model/ main/java/org/apache/rave/portal/model/util/ main/java/org/apache/rave/portal/repository/ main/java/org/apache/rave/portal/repository/impl/ main/ja...

Author: jasha
Date: Wed Aug 17 15:54:08 2011
New Revision: 1158787

URL: http://svn.apache.org/viewvc?rev=1158787&view=rev
Log:
RAVE-67 Enable search within the widget store.

Added:
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/util/SearchResult.java
Modified:
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Widget.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/WidgetRepository.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/JpaWidgetRepository.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/WidgetService.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultWidgetService.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/controller/WidgetStoreController.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ModelKeys.java
    incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/store.jsp
    incubator/rave/trunk/rave-portal/src/main/webapp/css/default.css
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/JpaWidgetRepositoryTest.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/WidgetServiceTest.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/controller/WidgetStoreControllerTest.java

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Widget.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Widget.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Widget.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Widget.java Wed Aug 17 15:54:08 2011
@@ -19,9 +19,19 @@
 package org.apache.rave.portal.model;
 
 import java.io.Serializable;
-import org.apache.rave.persistence.BasicEntity;
 
-import javax.persistence.*;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+
+import org.apache.rave.persistence.BasicEntity;
 
 /**
  * A widget
@@ -30,7 +40,10 @@ import javax.persistence.*;
 @Table(name="widget")
 @SequenceGenerator(name="widgetIdSeq", sequenceName = "widget_id_seq")
 @NamedQueries({
-        @NamedQuery(name = "Widget.getAll", query = "SELECT w from Widget w")
+        @NamedQuery(name = "Widget.getAll", query = "SELECT w from Widget w"),
+        @NamedQuery(name = "Widget.countAll", query = "SELECT count(w) FROM Widget w"),
+        @NamedQuery(name = "Widget.getByFreeText", query = "SELECT w FROM Widget w WHERE lower(w.title) LIKE :searchTerm OR w.description LIKE :description"),
+        @NamedQuery(name = "Widget.countByFreeText", query = "SELECT count(w) FROM Widget w WHERE lower(w.title) LIKE :searchTerm OR w.description LIKE :description")
 })
 public class Widget implements BasicEntity, Serializable {
     private static final long serialVersionUID = 1L;

Added: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/util/SearchResult.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/util/SearchResult.java?rev=1158787&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/util/SearchResult.java (added)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/util/SearchResult.java Wed Aug 17 15:54:08 2011
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.rave.portal.model.util;
+
+import java.util.List;
+
+import org.apache.rave.persistence.BasicEntity;
+
+/**
+ * Wrapper for search results.
+ */
+public class SearchResult<T extends BasicEntity> {
+    private List<T> resultSet;
+    private int pageSize;
+    private int totalResults;
+
+    public SearchResult(List<T> resultset, int totalResults) {
+        this.resultSet = resultset;
+        this.totalResults = totalResults;
+    }
+
+    /**
+     * @return a List of beans matching the original search query
+     */
+    public List<T> getResultSet() {
+        return resultSet;
+    }
+
+    /**
+     * The total number of items that match the query.
+     * <p/>
+     * Is useful for paging when the resultset is smaller than the total possible matches.
+     *
+     * @return total number of items that match the query
+     */
+    public int getTotalResults() {
+        return totalResults;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(int pageSize) {
+        this.pageSize = pageSize;
+    }
+}

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/WidgetRepository.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/WidgetRepository.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/WidgetRepository.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/WidgetRepository.java Wed Aug 17 15:54:08 2011
@@ -18,16 +18,40 @@
  */
 package org.apache.rave.portal.repository;
 
+import java.util.List;
+
 import org.apache.rave.persistence.Repository;
 import org.apache.rave.portal.model.Widget;
 
-import java.util.List;
-
 public interface WidgetRepository extends Repository<Widget> {
     /**
      * Gets a list of all widgets in the repository
+     *
      * @return a valid List
      */
     List<Widget> getAll();
 
+    /**
+     * @return the total number of {@link Widget}'s in the repository. Useful for paging.
+     */
+    int getCountAll();
+
+    /**
+     * Gets a List of {@link Widget}'s by performing a free text search
+     *
+     * @param searchTerm free text search term
+     * @param offset     start point within the resultset (for paging)
+     * @param pageSize   maximum number of items to be returned (for paging)
+     * @return valid list of widgets, can be empty
+     */
+    List<Widget> getByFreeTextSearch(String searchTerm, int offset, int pageSize);
+
+    /**
+     * Counts the total number of {@link Widget}'s that match the search term. Useful for paging.
+     *
+     * @param searchTerm free text search term
+     * @return total number of {@link Widget}'s that match the search term
+     */
+    int getCountFreeTextSearch(String searchTerm);
+
 }
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/JpaWidgetRepository.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/JpaWidgetRepository.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/JpaWidgetRepository.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/JpaWidgetRepository.java Wed Aug 17 15:54:08 2011
@@ -20,16 +20,18 @@
 package org.apache.rave.portal.repository.impl;
 
 
+import java.util.List;
+
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+
 import org.apache.rave.persistence.jpa.AbstractJpaRepository;
 import org.apache.rave.portal.model.Widget;
 import org.apache.rave.portal.repository.WidgetRepository;
 import org.springframework.stereotype.Repository;
 
-import javax.persistence.TypedQuery;
-import java.util.List;
-
 @Repository
-public class JpaWidgetRepository extends AbstractJpaRepository<Widget> implements WidgetRepository{
+public class JpaWidgetRepository extends AbstractJpaRepository<Widget> implements WidgetRepository {
 
     public JpaWidgetRepository() {
         super(Widget.class);
@@ -40,4 +42,32 @@ public class JpaWidgetRepository extends
         TypedQuery<Widget> query = manager.createNamedQuery("Widget.getAll", Widget.class);
         return query.getResultList();
     }
+
+    @Override
+    public int getCountAll() {
+        Query query = manager.createNamedQuery("Widget.countAll");
+        Number countResult=(Number) query.getSingleResult();
+        return countResult.intValue();
+    }
+
+    @Override
+    public List<Widget> getByFreeTextSearch(String searchTerm, int offset, int pageSize) {
+        TypedQuery<Widget> query = manager.createNamedQuery("Widget.getByFreeText", Widget.class);
+        query.setParameter("searchTerm", "%" + searchTerm.toLowerCase() + "%");
+        if (offset > 0) {
+            query.setFirstResult(offset);
+        }
+        if (pageSize > 0) {
+            query.setMaxResults(pageSize);
+        }
+        return query.getResultList();
+    }
+
+    @Override
+    public int getCountFreeTextSearch(String searchTerm) {
+        Query query = manager.createNamedQuery("Widget.countByFreeText");
+        query.setParameter("searchTerm", "%" + searchTerm.toLowerCase() + "%");
+        Number countResult=(Number) query.getSingleResult();
+        return countResult.intValue();
+    }
 }

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/WidgetService.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/WidgetService.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/WidgetService.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/WidgetService.java Wed Aug 17 15:54:08 2011
@@ -20,8 +20,7 @@
 package org.apache.rave.portal.service;
 
 import org.apache.rave.portal.model.Widget;
-
-import java.util.List;
+import org.apache.rave.portal.model.util.SearchResult;
 
 /**
  * Provides widget operations
@@ -29,13 +28,25 @@ import java.util.List;
 public interface WidgetService {
 
     /**
-     * Gets a list of widgets that a user can add to their context
-     * @return valid list of widgets
+     * Gets a {@link SearchResult} for {@link Widget}'s that a user can add to their context
+     *
+     * @return SearchResult
+     */
+    SearchResult<Widget> getAllWidgets();
+
+    /**
+     * Gets a SearchResult for {@link Widget}'s by performing a free text search
+     *
+     * @param searchTerm free text search term
+     * @param offset     start point within the resultset (for paging)
+     * @param pageSize   maximum number of items to be returned (for paging)
+     * @return SearchResult
      */
-    List<Widget> getAllWidgets();
+    SearchResult<Widget> getWidgetsByFreeTextSearch(String searchTerm, int offset, int pageSize);
 
     /**
      * Gets the detailed metadata for a widget
+     *
      * @param id the Id of the widget
      * @return a valid widget if one exists for the given id; null otherwise
      */

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultWidgetService.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultWidgetService.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultWidgetService.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultWidgetService.java Wed Aug 17 15:54:08 2011
@@ -19,14 +19,16 @@
 
 package org.apache.rave.portal.service.impl;
 
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
 import org.apache.rave.portal.model.Widget;
+import org.apache.rave.portal.model.util.SearchResult;
 import org.apache.rave.portal.repository.WidgetRepository;
 import org.apache.rave.portal.service.WidgetService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.List;
-
 @Service
 public class DefaultWidgetService implements WidgetService {
 
@@ -38,8 +40,28 @@ public class DefaultWidgetService implem
     }
 
     @Override
-    public List<Widget> getAllWidgets() {
-        return widgetRepository.getAll();
+    public SearchResult<Widget> getAllWidgets() {
+        int count = widgetRepository.getCountAll();
+        List<Widget> widgets = widgetRepository.getAll();
+        return new SearchResult<Widget>(widgets, count);
+    }
+
+    @Override
+    public SearchResult<Widget> getWidgetsByFreeTextSearch(String searchTerm, int offset, int pageSize) {
+        int count;
+        List<Widget> widgets;
+        if (StringUtils.isBlank(searchTerm)) {
+            count = widgetRepository.getCountAll();
+            // TODO a getAll with paging support
+            widgets = widgetRepository.getAll();
+
+        } else {
+            count = widgetRepository.getCountFreeTextSearch(searchTerm);
+            widgets = widgetRepository.getByFreeTextSearch(searchTerm, offset, pageSize);
+        }
+        final SearchResult<Widget> searchResult = new SearchResult<Widget>(widgets, count);
+        searchResult.setPageSize(pageSize);
+        return searchResult;
     }
 
     @Override

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/controller/WidgetStoreController.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/controller/WidgetStoreController.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/controller/WidgetStoreController.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/controller/WidgetStoreController.java Wed Aug 17 15:54:08 2011
@@ -31,9 +31,12 @@ import org.springframework.web.bind.anno
 import org.springframework.web.bind.annotation.RequestParam;
 
 @Controller
-@RequestMapping(value = {"/store/*", "/store" })
+@RequestMapping(value = {"/store/*", "/store"})
 public class WidgetStoreController {
 
+    // TODO make this value configurable through some management interface
+    private static final int MAXIMUM_WIDGETS_PER_PAGE = 10;
+
     private final WidgetService widgetService;
 
     @Autowired
@@ -43,7 +46,8 @@ public class WidgetStoreController {
 
     /**
      * Views the main page of the widget store
-     * @param model model map
+     *
+     * @param model           model map
      * @param referringPageId the source {@link org.apache.rave.portal.model.Page } ID
      * @return the view name of the main store page
      */
@@ -56,8 +60,9 @@ public class WidgetStoreController {
 
     /**
      * Views details of the specified widget
-     * @param model model map
-     * @param widgetId ID of the {@link org.apache.rave.portal.model.Widget } to view
+     *
+     * @param model           model map
+     * @param widgetId        ID of the {@link org.apache.rave.portal.model.Widget } to view
      * @param referringPageId the source {@link org.apache.rave.portal.model.Page } ID
      * @return the view name of the widget detail page
      */
@@ -67,4 +72,26 @@ public class WidgetStoreController {
         model.addAttribute(ModelKeys.REFERRING_PAGE_ID, referringPageId);
         return ViewNames.WIDGET;
     }
+
+    /**
+     * Performs a search in the widget store
+     *
+     * @param model           {@link Model} map
+     * @param referringPageId the source {@link org.apache.rave.portal.model.Page } ID
+     * @param searchTerm          free text searchTerm query
+     * @param offset          offset within the total amount of results (to enable paging)
+     * @return the view name of the main store page
+     */
+    @RequestMapping(method = RequestMethod.GET, value = "/store/search")
+    public String viewSearchResult(Model model, @RequestParam long referringPageId,
+                                   @RequestParam String searchTerm,
+                                   @RequestParam(required = false, defaultValue = "0") int offset) {
+        model.addAttribute(ModelKeys.WIDGETS,
+                widgetService.getWidgetsByFreeTextSearch(searchTerm, offset,
+                        MAXIMUM_WIDGETS_PER_PAGE));
+        model.addAttribute(ModelKeys.REFERRING_PAGE_ID, referringPageId);
+        model.addAttribute(ModelKeys.SEARCH_TERM, searchTerm);
+        model.addAttribute(ModelKeys.OFFSET, offset);
+        return ViewNames.STORE;
+    }
 }

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ModelKeys.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ModelKeys.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ModelKeys.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ModelKeys.java Wed Aug 17 15:54:08 2011
@@ -31,5 +31,7 @@ public class ModelKeys {
     public static final String REFERRING_PAGE_ID = "referringPageId";
     public static final String OPENSOCIAL_ENVIRONMENT = "openSocialEnv";
     public static final String NEW_USER = "newUser"; //a new user instance created
-	 public static final String USER_PROFILE="userProfile";
+    public static final String USER_PROFILE="userProfile";
+    public static final String SEARCH_TERM = "searchTerm";
+    public static final String OFFSET = "offset";
 }
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/store.jsp
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/store.jsp?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/store.jsp (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/store.jsp Wed Aug 17 15:54:08 2011
@@ -21,6 +21,7 @@
 <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
 <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
 <%@ taglib tagdir="/WEB-INF/tags" prefix="rave"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
 
 <rave:rave_generic_page pageTitle="Widget Store - Rave">
 <div id="header">
@@ -29,41 +30,95 @@
             <a href="<spring:url value="/index.html" />">Back to Rave</a>
         </span>
     </div>
+    <h1>Widget Store</h1>
 </div>
 
-
-
-
 <div id="content">
-    <c:forEach var="widget" items="${widgets}">
-        <div class="storeItem">
-            <div class="storeItemLeft">
-                <c:if test="${not empty widget.thumbnailUrl}">
-                    <img class="storeWidgetThumbnail" src="${widget.thumbnailUrl}"
-                         title="${widget.title}" alt="${widget.title}"
-                         width="120" height="60"/>
-                </c:if>
-                <div class="widgetType">${widget.type}</div>
-            </div>
-            <div class="storeItemCenter">
-                <div id="widgetAdded_${widget.id}" class="storeButton">
-                    <button class="storeItemButton"
-                            id="addWidget_${widget.id}"
-                            onclick="rave.api.rpc.addWidgetToPage({widgetId: ${widget.id}, pageId: ${referringPageId}});">
-                        Add to Page
-                    </button>
-                </div>
-                <a class="secondaryPageItemTitle"
-                   href="<spring:url value="/app/store/widget/${widget.id}" />?referringPageId=${referringPageId}">${widget.title}
-                </a>
+    
+    <div class="storeSearch">
+        <form action="<c:url value="/app/store/search"/>" method="GET">
+            <fieldset>
+                <input type="hidden" name="referringPageId" value="${referringPageId}">
+                <p>
+                    <label for="searchTerm">Search in widget store</label>
+                    <input type="search" id="searchTerm" name="searchTerm" value="<c:out value="${searchTerm}"/>"/>
+                    <input type="submit" value="Search">
+                </p>
+            </fieldset>
+        </form>
+        <div class="clear-float">&nbsp;</div>
+        <c:choose>
+            <c:when test="${empty searchTerm and (empty widgets or widgets.totalResults eq 0)}">
+                <%-- Empty db --%>
+                <h2>No widgets found</h2>
+            </c:when>
+            <c:when test="${empty searchTerm}">
+                <%-- TODO: introduce paging here --%>
+                <h2>Showing ${offset + 1} - ${offset + fn:length(widgets.resultSet)} of ${widgets.totalResults} widgets</h2>
+            </c:when>
+            <c:when test="${not empty searchTerm and widgets.totalResults eq 0}">
+                <h2>No widgets found for '<c:out value="${searchTerm}"/>'</h2>
+            </c:when>
+            <c:otherwise>
+                <h2>Showing ${offset + 1} - ${offset + fn:length(widgets.resultSet)} of ${widgets.totalResults} widgets that match '<c:out value="${searchTerm}"/>'</h2>
+            </c:otherwise>
+        </c:choose>
+    </div>
 
-                <div class="storeWidgetAuthor">By:
-                ${widget.author}</div>
-                <div class="storeWidgetDesc">${widget.description}</div>
+    <%--@elvariable id="widgets" type="org.apache.rave.portal.model.util.SearchResult"--%>
+    <c:if test="${widgets.totalResults gt 0}">
+        <ul class="storeItems">
+            <c:forEach var="widget" items="${widgets.resultSet}">
+                <li class="storeItem">
+                    <div class="storeItemLeft">
+                        <c:if test="${not empty widget.thumbnailUrl}">
+                            <img class="storeWidgetThumbnail" src="${widget.thumbnailUrl}"
+                                 title="<c:out value="${widget.title}"/>" alt="Thumbnail for widget <c:out value="${widget.title}"/>"
+                                 width="120" height="60"/>
+                        </c:if>
+                        <div class="widgetType"><c:out value="${widget.type}"/></div>
+                    </div>
+                    <div class="storeItemCenter">
+                        <div id="widgetAdded_${widget.id}" class="storeButton">
+                            <button class="storeItemButton"
+                                    id="addWidget_${widget.id}"
+                                    onclick="rave.api.rpc.addWidgetToPage({widgetId: ${widget.id}, pageId: ${referringPageId}});">
+                                Add to Page
+                            </button>
+                        </div>
+                        <a class="secondaryPageItemTitle"
+                           href="<spring:url value="/app/store/widget/${widget.id}" />?referringPageId=${referringPageId}">
+                            <c:out value="${widget.title}"/>
+                        </a>
+
+                        <c:if test="${not empty widget.author}">
+                            <div class="storeWidgetAuthor">By: <c:out value="${widget.author}"/></div>
+                        </c:if>
+                        <c:if test="${not empty widget.description}">
+                            <div class="storeWidgetDesc"><c:out value="${widget.description}"/></div>
+                        </c:if>
+                    </div>
+                    <div class="clear-float" >&nbsp;</div>
+                </li>
+            </c:forEach>
+        </ul>
+        
+        <c:if test="${fn:length(widgets.resultSet) lt widgets.totalResults and widgets.pageSize gt 0}">
+            <div class="storeBox">
+            <ul class="paging">
+                <c:set var="nrOfPages" value="${widgets.totalResults / widgets.pageSize}"/>
+                <c:forEach var="i" begin="0" end="${nrOfPages}">
+                    <c:url var="pageUrl" value="/app/store/search">
+                        <c:param name="referringPageId" value="${referringPageId}"/>
+                        <c:param name="searchTerm" value="${searchTerm}"/>
+                        <c:param name="offset" value="${i * widgets.pageSize}"/>
+                    </c:url>
+                    <li><a href="<c:out value="${pageUrl}"/>">${i + 1}</a></li>
+                </c:forEach>
+            </ul>
             </div>
-            <div class="clear-float" >&nbsp;</div>
-        </div>
-    </c:forEach>
+        </c:if>
+    </c:if>
 </div>
 <script>
     var rave = rave || {
@@ -72,6 +127,6 @@
         }
     }
 </script>
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
+<script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.min.js"></script>
 <script src="<spring:url value="/script/rave_api.js"/>"></script>
 </rave:rave_generic_page>

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/css/default.css
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/css/default.css?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/css/default.css (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/css/default.css Wed Aug 17 15:54:08 2011
@@ -238,6 +238,18 @@ h1, h2, h3, h4, h5, h6 {
     border: 1px solid black;
 }
 
+.storeSearch, .storeBox {
+    margin: 5px auto;
+    width: 60%
+}
+.storeSearch form {
+    margin: 0;
+    float: right;
+}
+.storeSearch form p {
+    margin: 0;
+}
+
 #storeWidgetDetail {
     width: 100%;
 }
@@ -275,6 +287,13 @@ h1, h2, h3, h4, h5, h6 {
     padding-left: 10px;
 }
 
+ul.storeItems {
+    padding: 0;
+}
+
+ul.storeItems li{
+    list-style: none;
+}
 
 .storeItemLeft {
    padding-left: 10px;
@@ -293,10 +312,6 @@ h1, h2, h3, h4, h5, h6 {
     width: 50%;
 }
 
-.storeItems {
-    width: 50%;
-}
-
 .storeButton {
     float: right;
 }
@@ -311,6 +326,15 @@ h1, h2, h3, h4, h5, h6 {
     visibility: hidden;
 }
 
+ul.paging {
+    list-style: none;
+    padding: 0;
+}
+
+ul.paging li {
+    display: inline;
+}
+
 
 #navigation {
     border: 1px solid #e6ebed;

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/JpaWidgetRepositoryTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/JpaWidgetRepositoryTest.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/JpaWidgetRepositoryTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/JpaWidgetRepositoryTest.java Wed Aug 17 15:54:08 2011
@@ -32,12 +32,17 @@ import org.springframework.test.context.
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.transaction.annotation.Transactional;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertThat;
 
+/**
+ * Test class for {@link org.apache.rave.portal.repository.impl.JpaWidgetRepository}
+ */
 @Transactional
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = {"classpath:portal-test-dataContext.xml", "classpath:portal-test-applicationContext.xml"})
@@ -63,9 +68,27 @@ public class JpaWidgetRepositoryTest {
     }
 
     @Test
+    public void getByFreeTextSearch() {
+        List<Widget> widgets = repository.getByFreeTextSearch("gAdGet", 1, 1);
+        assertEquals(1, widgets.size());
+    }
+
+    @Test
+    public void countFreeTextSearch() {
+        int count = repository.getCountFreeTextSearch("gAdGet");
+        assertTrue(count >= 2);
+    }
+
+    @Test
     public void getAll() {
         List<Widget> widgets = repository.getAll();
         assertThat(widgets, is(notNullValue()));
         assertThat(widgets.size() > 4, is(true));
     }
+
+    @Test
+    public void countAll() {
+        int count = repository.getCountAll();
+        assertTrue(count > 4);
+    }
 }

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/WidgetServiceTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/WidgetServiceTest.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/WidgetServiceTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/WidgetServiceTest.java Wed Aug 17 15:54:08 2011
@@ -19,19 +19,28 @@
 
 package org.apache.rave.portal.service;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.rave.portal.model.Widget;
+import org.apache.rave.portal.model.util.SearchResult;
 import org.apache.rave.portal.repository.WidgetRepository;
 import org.apache.rave.portal.service.impl.DefaultWidgetService;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.easymock.EasyMock.*;
-import static org.hamcrest.CoreMatchers.*;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
+/**
+ * Test for {@link DefaultWidgetService}
+ */
 public class WidgetServiceTest {
 
     private WidgetService service;
@@ -49,7 +58,7 @@ public class WidgetServiceTest {
         expect(repository.getAll()).andReturn(widgets);
         replay(repository);
 
-        List<Widget> result = service.getAllWidgets();
+        List<Widget> result = service.getAllWidgets().getResultSet();
         assertThat(result, is(sameInstance(widgets)));
     }
 
@@ -65,6 +74,28 @@ public class WidgetServiceTest {
     }
 
     @Test
+    public void getWidgetsForSearchTerm() {
+        String searchTerm = "gAdGet";
+        int offset = 0;
+        int pagesize = 10;
+        int totalResults = 2;
+        Widget widget = new Widget();
+        widget.setId(1L);
+        List<Widget> widgets = new ArrayList<Widget>();
+        widgets.add(widget);
+        
+        expect(repository.getCountFreeTextSearch(searchTerm)).andReturn(totalResults);
+        expect(repository.getByFreeTextSearch(searchTerm, offset, pagesize)).andReturn(widgets);
+        replay(repository);
+
+        SearchResult<Widget> result = service.getWidgetsByFreeTextSearch(searchTerm, offset, pagesize);
+        assertEquals(widget, result.getResultSet().get(0));
+        assertEquals(totalResults, result.getTotalResults());
+        assertEquals(pagesize, result.getPageSize());
+
+    }
+
+    @Test
     public void getWidget_null() {
         expect(repository.get(1L)).andReturn(null);
         replay(repository);

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/controller/WidgetStoreControllerTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/controller/WidgetStoreControllerTest.java?rev=1158787&r1=1158786&r2=1158787&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/controller/WidgetStoreControllerTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/controller/WidgetStoreControllerTest.java Wed Aug 17 15:54:08 2011
@@ -19,7 +19,12 @@
 
 package org.apache.rave.portal.web.controller;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.rave.portal.model.Widget;
+import org.apache.rave.portal.model.util.SearchResult;
 import org.apache.rave.portal.service.WidgetService;
 import org.apache.rave.portal.web.util.ModelKeys;
 import org.apache.rave.portal.web.util.ViewNames;
@@ -28,13 +33,20 @@ import org.junit.Test;
 import org.springframework.ui.ExtendedModelMap;
 import org.springframework.ui.Model;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.easymock.EasyMock.*;
-import static org.hamcrest.CoreMatchers.*;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
+/**
+ * Test class for {@link WidgetStoreController}
+ */
 public class WidgetStoreControllerTest {
 
     private static final long WIDGET_ID = 1L;
@@ -53,8 +65,9 @@ public class WidgetStoreControllerTest {
     public void view() {
         Model model = new ExtendedModelMap();
         List<Widget> widgets = new ArrayList<Widget>();
+        SearchResult<Widget> emptyResult = new SearchResult<Widget>(widgets, 0);
 
-        expect(widgetService.getAllWidgets()).andReturn(widgets);
+        expect(widgetService.getAllWidgets()).andReturn(emptyResult);
         replay(widgetService);
 
         String view = controller.view(model, REFERRER_ID);
@@ -62,8 +75,8 @@ public class WidgetStoreControllerTest {
         verify(widgetService);
         assertThat(view, is(equalTo(ViewNames.STORE)));
         assertThat(model.containsAttribute(ModelKeys.WIDGETS), is(true));
-        assertThat((Long)model.asMap().get(ModelKeys.REFERRING_PAGE_ID), is(equalTo(REFERRER_ID)));
-        assertThat(widgets, is(sameInstance(widgets)));
+        assertThat((Long) model.asMap().get(ModelKeys.REFERRING_PAGE_ID), is(equalTo(REFERRER_ID)));
+        assertThat(widgets, is(sameInstance(emptyResult.getResultSet())));
     }
 
     @Test
@@ -79,7 +92,36 @@ public class WidgetStoreControllerTest {
         verify(widgetService);
         assertThat(view, is(equalTo(ViewNames.WIDGET)));
         assertThat(model.containsAttribute(ModelKeys.WIDGET), is(true));
-        assertThat(((Widget)model.asMap().get(ModelKeys.WIDGET)), is(sameInstance(w)));
+        assertThat(((Widget) model.asMap().get(ModelKeys.WIDGET)), is(sameInstance(w)));
     }
 
+    @Test
+    public void searchWidgets() {
+        Model model = new ExtendedModelMap();
+
+        String searchTerm = "gAdGet";
+        
+        int offset = 0;
+        int pagesize = 10;
+        int totalResults = 2;
+        Widget widget = new Widget();
+        widget.setId(1L);
+        List<Widget> widgets = new ArrayList<Widget>();
+        widgets.add(widget);
+        SearchResult<Widget> result = new SearchResult<Widget>(widgets, totalResults);
+        result.setPageSize(pagesize);
+
+        expect(widgetService.getWidgetsByFreeTextSearch(searchTerm, offset, pagesize)).andReturn(result);
+        replay(widgetService);
+
+        String view = controller.viewSearchResult(model,REFERRER_ID, searchTerm, offset);
+        verify(widgetService);
+
+        assertEquals(ViewNames.STORE, view);
+        final Map<String,Object> modelMap = model.asMap();
+        assertEquals(searchTerm, modelMap.get(ModelKeys.SEARCH_TERM));
+        assertTrue(model.containsAttribute(ModelKeys.WIDGETS));
+        assertEquals(offset, modelMap.get(ModelKeys.OFFSET));
+        assertEquals(result, modelMap.get(ModelKeys.WIDGETS));
+    }
 }