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/24 14:25:30 UTC

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

Author: jasha
Date: Wed Aug 24 12:25:29 2011
New Revision: 1161066

URL: http://svn.apache.org/viewvc?rev=1161066&view=rev
Log:
RAVE-76 Users can submit a Widget for inclusion in the Widget Repository

Added:
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/validator/NewWidgetValidator.java
    incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/addwidget.jsp
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/validator/NewWidgetValidatorTest.java
Modified:
    incubator/rave/trunk/pom.xml
    incubator/rave/trunk/rave-portal/pom.xml
    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/ViewNames.java
    incubator/rave/trunk/rave-portal/src/main/resources/initial_data.sql
    incubator/rave/trunk/rave-portal/src/main/resources/messages.properties
    incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/applicationContext.xml
    incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/store.jsp
    incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/widget.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
    incubator/rave/trunk/rave-portal/src/test/resources/portal-test-applicationContext.xml

Modified: incubator/rave/trunk/pom.xml
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/pom.xml?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/pom.xml (original)
+++ incubator/rave/trunk/pom.xml Wed Aug 24 12:25:29 2011
@@ -54,6 +54,8 @@
         <log4j.version>1.2.16</log4j.version>
         <junit.version>4.7</junit.version>
         <commons-lang.version>2.6</commons-lang.version>
+        <!-- Don't update to commons-validator 1.3.1, it misses a dependency -->
+        <commons-validator.version>1.3.0</commons-validator.version>
         <icu4j.version>4.6.1</icu4j.version>
         <easymock.version>3.0</easymock.version>
         <com.h2database.version>1.3.154</com.h2database.version>
@@ -254,6 +256,13 @@
                 <version>${commons-lang.version}</version>
             </dependency>
 
+            <!-- Validation -->
+            <dependency>
+                <groupId>commons-validator</groupId>
+                <artifactId>commons-validator</artifactId>
+                <version>${commons-validator.version}</version>
+            </dependency>
+
             <!-- Rave artifacts -->
             <dependency>
                 <groupId>org.apache.rave</groupId>

Modified: incubator/rave/trunk/rave-portal/pom.xml
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/pom.xml?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/pom.xml (original)
+++ incubator/rave/trunk/rave-portal/pom.xml Wed Aug 24 12:25:29 2011
@@ -150,6 +150,12 @@
             <artifactId>h2</artifactId>
         </dependency>
 
+        <!-- Validation -->
+        <dependency>
+            <groupId>commons-validator</groupId>
+            <artifactId>commons-validator</artifactId>
+        </dependency>
+
         <!-- JSTL -->
         <dependency>
             <groupId>org.glassfish.web</groupId>

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -56,14 +56,16 @@ import org.apache.rave.persistence.Basic
         @NamedQuery(name = Widget.WIDGET_GET_BY_STATUS_AND_FREE_TEXT,
                 query = "SELECT w FROM Widget w WHERE w.widgetStatus = :widgetStatus AND lower(w.title) LIKE :searchTerm OR w.description LIKE :description"),
         @NamedQuery(name = Widget.WIDGET_COUNT_BY_STATUS_AND_FREE_TEXT,
-                query = "SELECT count(w) FROM Widget w WHERE w.widgetStatus = :widgetStatus AND lower(w.title) LIKE :searchTerm OR w.description LIKE :description")
+                query = "SELECT count(w) FROM Widget w WHERE w.widgetStatus = :widgetStatus AND lower(w.title) LIKE :searchTerm OR w.description LIKE :description"),
 
+        @NamedQuery(name = Widget.WIDGET_GET_BY_URL, query = "SELECT w FROM Widget w WHERE w.url = :url")
 })
 public class Widget implements BasicEntity, Serializable {
     private static final long serialVersionUID = 1L;
 
     public static final String PARAM_SEARCH_TERM = "searchTerm";
     public static final String PARAM_STATUS = "widgetStatus";
+    public static final String PARAM_URL = "url";
 
     public static final String WIDGET_GET_ALL = "Widget.getAll";
     public static final String WIDGET_COUNT_ALL = "Widget.countAll";
@@ -75,6 +77,7 @@ public class Widget implements BasicEnti
             "Widget.getByStatusAndFreeText";
     public static final String WIDGET_COUNT_BY_STATUS_AND_FREE_TEXT =
             "Widget.countByStatusAndFreeText";
+    public static final String WIDGET_GET_BY_URL = "Widget.getByUrl";
 
     @Id @Column(name="id")
     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "widgetIdSeq")
@@ -88,7 +91,7 @@ public class Widget implements BasicEnti
     private String title;
     //private InternationalString title;
 
-    @Basic @Column(name="url")
+    @Basic @Column(name="url", unique = true)
     private String url;
 
     @Basic @Column(name="thumbnail_url")

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -105,4 +105,12 @@ public interface WidgetRepository extend
      * @return total number of {@link Widget}'s that match the search criteria
      */
     int getCountByStatusAndFreeText(WidgetStatus widgetStatus, String searchTerm);
+
+    /**
+     * Searches for a Widget by its url
+     *
+     * @param widgetUrl (unique) url of the Widget
+     * @return {@link Widget} if it can be found, otherwise {@literal null}
+     */
+    Widget getByUrl(String widgetUrl);
 }
\ 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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -25,6 +25,7 @@ import java.util.List;
 import javax.persistence.Query;
 import javax.persistence.TypedQuery;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.rave.persistence.jpa.AbstractJpaRepository;
 import org.apache.rave.portal.model.Widget;
 import org.apache.rave.portal.model.WidgetStatus;
@@ -115,6 +116,21 @@ public class JpaWidgetRepository extends
         return countResult.intValue();
     }
 
+    @Override
+    public Widget getByUrl(String widgetUrl) {
+        if (StringUtils.isBlank(widgetUrl)) {
+            throw new IllegalArgumentException("Widget URL must not be empty");
+        }
+        
+        TypedQuery<Widget> query = manager.createNamedQuery(Widget.WIDGET_GET_BY_URL, Widget.class);
+        // url is a unique field, so no paging needed
+        query.setParameter(Widget.PARAM_URL, widgetUrl);
+        final List<Widget> resultList = query.getResultList();
+        if (resultList.isEmpty()) {
+            return null;
+        }
+        return resultList.get(0);
+    }
 
     /**
      * Performs a query with a limit and offset

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -84,4 +84,21 @@ public interface WidgetService {
      */
     SearchResult<Widget> getPublishedWidgetsByFreeTextSearch(String searchTerm,
                                                              int offset, int pageSize);
+
+    /**
+     * Gets a Widget by its (unique) url
+     *
+     * @param widgetUrl url of the Widget
+     * @return {@link Widget} if it exists, otherwise {@literal null}
+     */
+    Widget getWidgetByUrl(String widgetUrl);
+
+
+    /**
+     * Persists a new {@link Widget} if it is not already present in the store
+     *
+     * @param widget new Widget to store
+     * @return Widget if it is new and can be stored, otherwise {@literal null}
+     */
+    Widget registerNewWidget(Widget widget);
 }

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -27,12 +27,16 @@ import org.apache.rave.portal.model.Widg
 import org.apache.rave.portal.model.util.SearchResult;
 import org.apache.rave.portal.repository.WidgetRepository;
 import org.apache.rave.portal.service.WidgetService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 @Service
 public class DefaultWidgetService implements WidgetService {
 
+    private static Logger logger = LoggerFactory.getLogger(DefaultWidgetService.class);
+
     private final WidgetRepository widgetRepository;
 
     @Autowired
@@ -104,4 +108,18 @@ public class DefaultWidgetService implem
         searchResult.setPageSize(pageSize);
         return searchResult;
     }
+
+    @Override
+    public Widget getWidgetByUrl(String widgetUrl) {
+        return widgetRepository.getByUrl(widgetUrl);
+    }
+
+    @Override
+    public Widget registerNewWidget(Widget widget) {
+        if (getWidgetByUrl(widget.getUrl()) != null) {
+            logger.debug("Trying to add an existing widget for url {}", widget.getUrl());
+            return null;
+        }
+        return widgetRepository.save(widget);
+    }
 }

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -19,12 +19,17 @@
 
 package org.apache.rave.portal.web.controller;
 
+import org.apache.rave.portal.model.Widget;
+import org.apache.rave.portal.model.WidgetStatus;
 import org.apache.rave.portal.service.WidgetService;
 import org.apache.rave.portal.web.util.ModelKeys;
 import org.apache.rave.portal.web.util.ViewNames;
+import org.apache.rave.portal.web.validator.NewWidgetValidator;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -39,9 +44,12 @@ public class WidgetStoreController {
 
     private final WidgetService widgetService;
 
+    private final NewWidgetValidator widgetValidator;
+
     @Autowired
-    public WidgetStoreController(WidgetService widgetService) {
+    public WidgetStoreController(WidgetService widgetService, NewWidgetValidator validator) {
         this.widgetService = widgetService;
+        this.widgetValidator = validator;
     }
 
     /**
@@ -85,7 +93,7 @@ public class WidgetStoreController {
      * @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")
+    @RequestMapping(method = RequestMethod.GET, value = "search")
     public String viewSearchResult(Model model, @RequestParam long referringPageId,
                                    @RequestParam String searchTerm,
                                    @RequestParam(required = false, defaultValue = "0") int offset) {
@@ -97,4 +105,46 @@ public class WidgetStoreController {
         model.addAttribute(ModelKeys.OFFSET, offset);
         return ViewNames.STORE;
     }
+
+    /**
+     * Shows the Add new Widget form
+     *
+     * @param model {@link Model}
+     * @return the view name of the Add new Widget form
+     */
+    @RequestMapping(method = RequestMethod.GET, value = "addwidget")
+    public String viewAddWidgetForm(Model model) {
+        final Widget widget = new Widget();
+        model.addAttribute(ModelKeys.WIDGET, widget);
+        return ViewNames.ADD_WIDGET_FORM;
+    }
+
+    /**
+     * Validates the form input, if valid, tries to store the Widget data
+     *
+     * @param widget  {@link Widget} as submitted by the user
+     * @param results {@link BindingResult}
+     * @param model   {@link Model}
+     * @return if successful the view name of the widget, otherwise the form
+     */
+    @RequestMapping(method = RequestMethod.POST, value = "doaddwidget")
+    public String viewAddWidgetResult(@ModelAttribute Widget widget, BindingResult results,
+                                      Model model) {
+        widgetValidator.validate(widget, results);
+        if (results.hasErrors()) {
+            model.addAttribute(ModelKeys.WIDGET, widget);
+            return ViewNames.ADD_WIDGET_FORM;
+        }
+        widget.setWidgetStatus(WidgetStatus.PREVIEW);
+
+        final Widget storedWidget = widgetService.registerNewWidget(widget);
+        if (storedWidget == null) {
+            results.reject("page.addwidget.result.exists");
+            model.addAttribute(ModelKeys.WIDGET, widget);
+            return ViewNames.ADD_WIDGET_FORM;
+        }
+
+        model.addAttribute(ModelKeys.WIDGET, storedWidget);
+        return ViewNames.WIDGET;
+    }
 }

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ViewNames.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ViewNames.java?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ViewNames.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/util/ViewNames.java Wed Aug 24 12:25:29 2011
@@ -27,6 +27,7 @@ public class ViewNames {
     public static final String HOME = "home";
     public static final String STORE = "store";
     public static final String WIDGET = "widget";
+    public static final String ADD_WIDGET_FORM = "addwidget";
     public static final String NEW_ACCOUNT = "newaccount";
     public static final String USER_PROFILE = "userProfile";
     public static final String REDIRECT = "redirect:/";

Added: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/validator/NewWidgetValidator.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/validator/NewWidgetValidator.java?rev=1161066&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/validator/NewWidgetValidator.java (added)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/validator/NewWidgetValidator.java Wed Aug 24 12:25:29 2011
@@ -0,0 +1,97 @@
+/*
+ * 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.web.validator;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.validator.UrlValidator;
+import org.apache.rave.portal.model.Widget;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+/**
+ * Validator for adding a new {@link Widget}
+ */
+public class NewWidgetValidator implements Validator {
+    private static final String FIELD_URL = "url";
+
+    private UrlValidator validator;
+
+    public NewWidgetValidator() {
+        super();
+        String[] allowedSchemes = {"http", "https"};
+        validator = new UrlValidator(allowedSchemes);
+    }
+
+    /**
+     * Supports {@link Widget}
+     * <p/>
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean supports(Class<?> clazz) {
+        return Widget.class.isAssignableFrom(clazz);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void validate(Object target, Errors errors) {
+        Widget widget = (Widget) target;
+
+        validateRequiredFields(errors);
+        validateUrlFields(widget, errors);
+    }
+
+    /**
+     * Checks if the required fields contain a value
+     *
+     * @param errors {@link Errors}
+     */
+    private void validateRequiredFields(Errors errors) {
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "widget.title.required");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, FIELD_URL, "widget.url.required");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "type", "widget.type.required");
+    }
+
+    /**
+     * Validates fields that may contain a URL
+     *
+     * @param widget {@link Widget} to validate
+     * @param errors {@link org.springframework.validation.Errors}
+     */
+    private void validateUrlFields(Widget widget, Errors errors) {
+        String url = widget.getUrl();
+        if (StringUtils.isNotBlank(url) && !validator.isValid(url)) {
+            errors.rejectValue(FIELD_URL, "widget.url.malformed");
+        }
+
+        String screenshotUrl = widget.getScreenshotUrl();
+        if (StringUtils.isNotBlank(screenshotUrl) && !validator.isValid(screenshotUrl)) {
+            errors.rejectValue("screenshotUrl", "widget.screenshotUrl.malformed");
+        }
+
+        String thumbnailUrl = widget.getThumbnailUrl();
+        if (StringUtils.isNotBlank(thumbnailUrl) && !validator.isValid(thumbnailUrl)) {
+            errors.rejectValue("thumbnailUrl", "widget.screenshotUrl.malformed");
+        }
+    }
+}

Modified: incubator/rave/trunk/rave-portal/src/main/resources/initial_data.sql
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/resources/initial_data.sql?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/resources/initial_data.sql (original)
+++ incubator/rave/trunk/rave-portal/src/main/resources/initial_data.sql Wed Aug 24 12:25:29 2011
@@ -75,8 +75,12 @@ values(set(@tabnews_widget_id, next valu
 -- end widget data ----
 
 -- hamster widget
-insert into widget (id, title, url, type, widget_status)
-values(set(@hamster_widget_id, next value for widget_id_seq), 'Pet Hamster', 'http://hosting.gmodules.com/ig/gadgets/file/112581010116074801021/hamster.xml', 'OpenSocial', 'PUBLISHED');
+insert into widget (id, title, url, type, widget_status, thumbnail_url)
+values(set(@hamster_widget_id, next value for widget_id_seq), 'Pet Hamster', 'http://hosting.gmodules.com/ig/gadgets/file/112581010116074801021/hamster.xml', 'OpenSocial', 'PUBLISHED', 'http://hosting.gmodules.com/ig/gadgets/file/112581010116074801021/hamsterThumb.png');
+
+-- another hamster widget
+insert into widget (id, title, url, type, description, author, widget_status, thumbnail_url, screenshot_url)
+values(set(@another_hamster_widget_id, next value for widget_id_seq), 'Herbie Hamster Virtual Pet', 'http://hosting.gmodules.com/ig/gadgets/file/109548057311228444554/hamster.xml', 'OpenSocial', 'A cute little hamster for you to feed and look after. Watch him follow your cursor around. Click on the more tab to treat him to a strawberry. Click him then put him on the wheel and watch him play! ***NEW: make Herbie hamster your very own!', 'Naj', 'PUBLISHED', 'http://sites.google.com/site/najartsist/pets-1/herbiet.png', 'http://sites.google.com/site/najartsist/herbie-hamster/herbie.png');
 
 -- slideshare widget
 insert into widget (id, title, url, type, widget_status)

Modified: incubator/rave/trunk/rave-portal/src/main/resources/messages.properties
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/resources/messages.properties?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/resources/messages.properties (original)
+++ incubator/rave/trunk/rave-portal/src/main/resources/messages.properties Wed Aug 24 12:25:29 2011
@@ -27,4 +27,40 @@ password.required=Password required
 password.invalid.lenght=Password must be atleast 4 characters long
 
 confirmPassword.required=Confirm Password required
-confirmPassword.mismatch=Password mismatch
\ No newline at end of file
+confirmPassword.mismatch=Password mismatch
+
+form.some.fields.required=Field marked with * are required
+
+page.general.back=Back to Rave
+page.general.titlesuffix= - Rave
+page.general.screenshot=screenshot
+page.general.thumbnail=thumbnail
+
+page.widget.addToPage=Add to Page
+page.widget.backToStore=Back to Store
+page.widget.widgetPreview=Widget Preview
+
+page.addwidget.pagetitle=Add new widget
+page.addwidget.form.header=Widget
+page.addwidget.form.submit=Add widget
+page.addwidget.result.exists=The widget you submitted already exists.
+page.addwidget.result.success=The widget was successfully added.
+
+widget.author=Author
+widget.description=Description
+widget.screenshotUrl=Screenshot
+widget.screenshotUrl.malformed=URL is malformed
+widget.thumbnailUrl=Thumbnail
+widget.thumbnailUrl.malformed=URL is malformed
+widget.title=Title
+widget.title.required=Title is required
+widget.type=Type
+widget.type.OpenSocial=OpenSocial
+widget.type.W3C=W3C Widget
+widget.type.required=Choose a widget type
+widget.url=Location (URL)
+widget.url.malformed=URL is malformed
+widget.url.required=URL is required
+widget.widgetStatus.PREVIEW=This widget is waiting for approval by the site administrator.
+widget.widgetStatus.PUBLISHED=
+

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/applicationContext.xml
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/applicationContext.xml?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/applicationContext.xml (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/applicationContext.xml Wed Aug 24 12:25:29 2011
@@ -111,7 +111,8 @@
     <!-- Validators -->
     <bean id="newAccountValidator" class="org.apache.rave.portal.web.validator.NewAccountValidator"/>
     <bean id="userProfileValidator" class="org.apache.rave.portal.web.validator.UserProfileValidator"/>
-	
+    <bean id="newWidgetValidator" class="org.apache.rave.portal.web.validator.NewWidgetValidator"/>
+
     <!-- Configuring messages.properties file --> 
     <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basename="messages" />
 

Added: incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/addwidget.jsp
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/addwidget.jsp?rev=1161066&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/addwidget.jsp (added)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/addwidget.jsp Wed Aug 24 12:25:29 2011
@@ -0,0 +1,109 @@
+<%--
+  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.
+  --%>
+
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib tagdir="/WEB-INF/tags" prefix="rave" %>
+
+<fmt:bundle basename="messages"/>
+<fmt:message key="page.addwidget.pagetitle" var="pagetitle"/>
+<fmt:message key="page.general.titlesuffix" var="titlesuffix"/>
+<rave:rave_generic_page pageTitle="${pagetitle}${titlesuffix}">
+    <div id="header">
+        <div class="header-a">
+        <span class="backToPage">
+            <a href="<spring:url value="/index.html" />"><fmt:message key="page.general.back"/></a>
+        </span>
+        </div>
+        <h1><fmt:message key="page.addwidget.pagetitle"/></h1>
+    </div>
+
+    <div id="content">
+        <h2><fmt:message key="page.addwidget.form.header"/></h2>
+        <form:errors path="widget" cssClass="error" element="p"/>
+        <form:form id="newWidgetForm" action="doaddwidget" commandName="widget" method="POST">
+            <fieldset>
+                <p><fmt:message key="form.some.fields.required"/></p>
+
+                <p>
+                    <form:label path="title"><fmt:message key="widget.title"/> *</form:label>
+                    <form:input path="title" cssClass="long" required="required" autofocus="autofocus" />
+                    <form:errors path="title" cssClass="error"/>
+                </p>
+
+                <p>
+                    <spring:bind path="url">
+                        <label for="url"><fmt:message key="widget.url"/> *</label>
+                        <input type="url" name="url" id="url"
+                               placeholder="http://example.com/widget.xml" required="required"
+                               class="long" value="<c:out value="${widget.url}"/>"/>
+                    </spring:bind>
+                    <form:errors path="url" cssClass="error"/>
+                </p>
+
+                <p>
+                    <label for="type1"><fmt:message key="widget.type"/> *</label>
+                    <label for="type1" class="formradio"><form:radiobutton path="type" value="OpenSocial"/>
+                        <fmt:message key="widget.type.OpenSocial"/></label>
+                    <label for="type2" class="formradio"><form:radiobutton path="type" value="W3C"/>
+                        <fmt:message key="widget.type.W3C"/></label>
+                    <form:errors path="type" cssClass="error"/>
+                </p>
+
+                <p>
+                    <form:label path="description"><fmt:message key="widget.description"/></form:label>
+                    <form:input path="description" cssClass="long"/>
+                    <form:errors path="description" cssClass="error"/>
+                </p>
+
+                <p>
+                    <spring:bind path="thumbnailUrl">
+                        <label for="thumbnailUrl"><fmt:message key="widget.thumbnailUrl"/></label>
+                        <input type="url" name="thumbnailUrl" id="thumbnailUrl"
+                               placeholder="http://example.com/thumbnail.png" class="long"
+                                value="<c:out value="${widget.thumbnailUrl}"/>"/>
+                    </spring:bind>
+                    <form:errors path="thumbnailUrl" cssClass="error"/>
+                </p>
+
+                <p>
+                    <spring:bind path="screenshotUrl">
+                        <label for="screenshotUrl"><fmt:message key="widget.screenshotUrl"/></label>
+                        <input type="url" name="screenshotUrl" id="screenshotUrl"
+                               placeholder="http://example.com/screenshot.png" class="long"
+                                value="<c:out value="${widget.screenshotUrl}"/>"/>
+                    </spring:bind>
+                    <form:errors path="screenshotUrl" cssClass="error"/>
+                </p>
+
+                <p>
+                    <form:label path="author"><fmt:message key="widget.author"/></form:label>
+                    <form:input path="author" cssClass="long"/>
+                    <form:errors path="author" cssClass="error"/>
+                </p>
+            </fieldset>
+            <fieldset>
+                <fmt:message key="page.addwidget.form.submit" var="submit"/>
+                <input type="submit" value="${submit}"/>
+            </fieldset>
+        </form:form>
+    </div>
+</rave:rave_generic_page>
\ 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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -30,6 +30,11 @@
             <a href="<spring:url value="/index.html" />">Back to Rave</a>
         </span>
     </div>
+    <div class="widget-a">
+        <span>
+            <a href="<spring:url value="store/addwidget"/>">Add widget</a>
+        </span>
+    </div>
     <h1>Widget Store</h1>
 </div>
 

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/widget.jsp
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/widget.jsp?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/widget.jsp (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/widget.jsp Wed Aug 24 12:25:29 2011
@@ -20,87 +20,100 @@
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@ 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"%>
-<jsp:useBean id="widget" scope="request" class="org.apache.rave.portal.model.Widget" />
-
-<rave:rave_generic_page pageTitle="${widget.title} - Rave">
-<div id="header">
-    <div class="header-a" >
+<%@ taglib tagdir="/WEB-INF/tags" prefix="rave" %>
+<jsp:useBean id="widget" scope="request" class="org.apache.rave.portal.model.Widget"/>
+<fmt:bundle basename="messages"/>
+<fmt:message var="titlesuffix" key="page.general.titlesuffix"/>
+<rave:rave_generic_page pageTitle="${widget.title}${titlesuffix}">
+    <div id="header">
+        <div class="header-a">
         <span class="backToPage">
-            <a href="<spring:url value="/index.html" />">Back to Rave</a>
-        </span>
-    </div>
-    <div class="widget-a">
-        <span>
-            <a href="<spring:url value="/app/store?referringPageId=${referringPageId}" />">Back to Store</a>
+            <a href="<spring:url value="/index.html" />"><fmt:message key="page.general.back"/></a>
         </span>
+        </div>
+        <c:if test="${not empty referringPageId}">
+            <div class="widget-a">
+            <span>
+                <a href="<spring:url value="/app/store?referringPageId=${referringPageId}" />">
+                    <fmt:message key="page.widget.backToStore"/>
+                </a>
+            </span>
+            </div>
+        </c:if>
+        <h1><c:out value="${widget.title}"/></h1>
     </div>
-</div>
 
 
-<div id="content">
-    <div id="widget-content">
-        <tr>
-            <td class="widgetDetailLeft">
+    <div id="content">
+        <div id="widget-content">
+
+            <div class="widgetDetailLeft">
                 <c:if test="${not empty widget.thumbnailUrl}">
                     <img class="storeWidgetThumbnail"
-                         src="${widget.thumbnailUrl}"
-                         title="${widget.title}}"
-                         alt="thumbnail"
+                         src="<c:out value="${widget.thumbnailUrl}"/>"
+                         title="<c:out value="${widget.title}"/>"
+                         alt="<fmt:message key="page.general.thumbnail"/>"
                          width="120" height="60"/>
                 </c:if>
+
                 <div class="widgetDetailMeta">
-                    <div class="widgetType">${widget.type}</div>
-                    <div class="widgetVersion"></div>
-                    <div class="widgetLastUpdated"></div>
+                    <p><fmt:message key="widget.type.${widget.type}" /></p>
                 </div>
-            </td>
-            <td class="widgetDetailRight" style="vertical-align: top; padding-left: 10px;">
+            </div>
+
+            <div class="widgetDetailCenter">
 
                 <div class="storeWidgetDetail">
-                    <div class="secondaryPageItemTitle"
-                         style="display: inline-block; vertical-align: top; padding-top: 5px;">${widget.title}
-                    </div>
-                    <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>
+                    <span class="secondaryPageItemTitle"><c:out value="${widget.title}"/></span>
+                    <c:choose>
+                        <c:when test="${widget.widgetStatus eq 'PUBLISHED'}">
+                            <div id="widgetAdded_${widget.id}" class="storeButton">
+                                <button class="storeItemButton"
+                                        id="addWidget_${widget.id}"
+                                        onclick="rave.api.rpc.addWidgetToPage({widgetId: ${widget.id}, pageId: ${referringPageId}});">
+                                    <fmt:message key="page.widget.addToPage"/>
+                                </button>
+                            </div>
+                        </c:when>
+                        <c:when test="${widget.widgetStatus eq 'PREVIEW'}">
+                            <p class="warn"><fmt:message key="widget.widgetStatus.PREVIEW"/></p>
+                        </c:when>
+                    </c:choose>
                 </div>
 
 
-                <div class="storeWidgetAuthor">
-                    By:
-                        ${widget.author}
-                </div>
-                <div class="storeWidgetDesc" >
-                    ${widget.description}
-                </div>
-            </td>
-            <td>
-                <div class="widgetScreenshotTitle">Widget Preview</div>
-                <div class="widgetScreenshot">
-                    <c:if test="${not empty widget.screenshotUrl}">
-                    <img src="${widget.screenshotUrl}"
-                         alt="screenshot"
-                         title="${widget.title} Screenshot"/>
-                    </c:if>
-                </div>
-            </td>
-        </tr>
-    </table>
+                <c:if test="${not empty widget.author}">
+                    <p class="storeWidgetAuthor">
+                        <fmt:message key="widget.author"/> <c:out value="${widget.author}"/>
+                    </p>
+                </c:if>
+
+                <c:if test="${not empty widget.description}">
+                    <p class="storeWidgetDesc"><c:out value="${widget.description}"/></p>
+                </c:if>
+            </div>
+            
+            <div class="widgetDetailRight">
+                <c:if test="${not empty widget.screenshotUrl}">
+                    <div class="widgetScreenshotTitle"><fmt:message key="page.widget.widgetPreview"/></div>
+                    <div class="widgetScreenshot">
+                        <img src="${widget.screenshotUrl}"
+                             alt="<fmt:message key="page.general.screenshot"/>"
+                             title="<c:out value="${widget.title}"/> <fmt:message key="page.general.screenshot"/>"/>
+                    </div>
+                </c:if>
+            </div>
+            <div class="clear-float" >&nbsp;</div>
+        </div>
     </div>
-</div>
-<script>
-    var rave = rave || {
-        getContext : function() {
-            return "<spring:url value="/app/" />";
+    <script>
+        var rave = rave || {
+            getContext : function() {
+                return "<spring:url value="/app/" />";
+            }
         }
-    }
-</script>
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
-<script src="<spring:url value="/script/rave_api.js"/>"></script>
+    </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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -277,16 +277,31 @@ h1, h2, h3, h4, h5, h6 {
 }
 
 .widgetDetailLeft {
-    width: 150px;
+   padding-left: 10px;
+   padding-right: 10px;
+   vertical-align: top;
+   width: 150px;
+   float: left;
+}
+
+.widgetDetailCenter {
+    padding-left: 10px;
+    padding-right: 10px;
     vertical-align: top;
+    float: left;
+    width: 50%;
 }
 
 .widgetDetailRight {
-    width: 60%;
-    vertical-align: top;
-    padding-left: 10px;
+  padding: 0 10px;
+  width: 300px;
+  vertical-align: top;
+  float: left;
 }
 
+.widgetScreenshot img {
+  max-width: 280px;
+}
 ul.storeItems {
     padding: 0;
 }
@@ -424,6 +439,8 @@ button.widget-toolbar-btn-prefs {
     display: none;
 }
 
+/* Form fields */
+
 fieldset{
     margin: 0;
     border:none
@@ -432,23 +449,43 @@ p label {
     width: 120px;
     float: left;
     display: block;
+    position: relative;
+}
+
+label.formradio {
+    float: none;
+    left: 0;
+    margin-left: 120px;
+    margin-bottom: 0.4em;
+    margin-right: 0;
+    position: static;
+    top: auto;
+    width: auto;
 }
 
 input.long {
     width:400px;
 }
 
+
 /* Associated with JQuery validation CSS */
 .error {
     color: #ff0000;
 }
 
-label.error {
+span.error, label.error {
     font-size: 10px;
     display: block;
     float: none;
     margin: 0.5em 0 auto 120px;
 }
+
+.warn {
+  font-weight: bold;
+}
+
+/* End of form fields */
+
 /*
 .rave-ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: #F2F2F2; height: 800px; }
 .rave-ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; background: #666666}

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -29,6 +29,8 @@ import org.apache.rave.portal.model.Widg
 import org.apache.rave.portal.model.WidgetStatus;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -37,6 +39,7 @@ import org.springframework.transaction.a
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
@@ -52,6 +55,8 @@ import static org.junit.Assert.assertTha
         "classpath:portal-test-applicationContext.xml"})
 public class JpaWidgetRepositoryTest {
 
+    private static Logger logger = LoggerFactory.getLogger(JpaWidgetRepositoryTest.class);
+    
     @PersistenceContext
     private EntityManager sharedManager;
 
@@ -72,6 +77,25 @@ public class JpaWidgetRepositoryTest {
     }
 
     @Test
+    public void getByUrl_valid() {
+        final String widgetUrl =
+                "http://hosting.gmodules.com/ig/gadgets/file/112581010116074801021/hamster.xml";
+        final Widget widget = repository.getByUrl(widgetUrl);
+        assertNotNull(widget);
+        assertEquals(widgetUrl, widget.getUrl());
+    }
+
+    @Test
+    public void getByUrl_empty() {
+        try{
+            repository.getByUrl("");
+            fail();
+        } catch (IllegalArgumentException e) {
+            logger.debug("Expected to fail on empty URL", e.getMessage());
+        }
+    }
+
+    @Test
     public void getByFreeTextSearch() {
         List<Widget> widgets = repository.getByFreeTextSearch("gAdGet", 1, 1);
         assertEquals(1, widgets.size());

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -37,6 +37,8 @@ import static org.hamcrest.CoreMatchers.
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.CoreMatchers.sameInstance;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThat;
 
@@ -167,4 +169,45 @@ public class WidgetServiceTest {
 
     }
 
+    @Test
+    public void getWidgetByUrl() {
+        final String widgetUrl =
+                "http://hosting.gmodules.com/ig/gadgets/file/112581010116074801021/hamster.xml";
+        Widget widget = new Widget();
+        widget.setUrl(widgetUrl);
+        expect(repository.getByUrl(widgetUrl)).andReturn(widget);
+        replay(repository);
+
+        Widget result = service.getWidgetByUrl(widgetUrl);
+        assertNotNull(result);
+        assertEquals(result.getUrl(), widgetUrl);
+    }
+
+    @Test
+    public void registerNewWidget() {
+        final String widgetUrl =
+                "http://example.com/newwidget.xml";
+        Widget widget = new Widget();
+        widget.setUrl(widgetUrl);
+        expect(repository.getByUrl(widgetUrl)).andReturn(null);
+        expect(repository.save(widget)).andReturn(widget);
+        replay(repository);
+
+        Widget savedWidget = service.registerNewWidget(widget);
+        assertNotNull(savedWidget);
+        assertEquals(widget.getId(), savedWidget.getId());
+    }
+
+    @Test
+    public void registerExistingWidgetAsNew() {
+        final String widgetUrl =
+                "http://hosting.gmodules.com/ig/gadgets/file/112581010116074801021/hamster.xml";
+        Widget widget = new Widget();
+        widget.setUrl(widgetUrl);
+        expect(repository.getByUrl(widgetUrl)).andReturn(widget);
+        replay(repository);
+
+        Widget noWidget = service.registerNewWidget(widget);
+        assertNull("Widget already exists", noWidget);
+    }
 }

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=1161066&r1=1161065&r2=1161066&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 24 12:25:29 2011
@@ -19,20 +19,29 @@
 
 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.WidgetStatus;
 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;
+import org.apache.rave.portal.web.validator.NewWidgetValidator;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.ui.ExtendedModelMap;
 import org.springframework.ui.Model;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.validation.BindingResult;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
 import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
@@ -40,9 +49,8 @@ import static org.easymock.EasyMock.veri
 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}
@@ -57,7 +65,8 @@ public class WidgetStoreControllerTest {
     @Before
     public void setup() {
         widgetService = createNiceMock(WidgetService.class);
-        controller = new WidgetStoreController(widgetService);
+        NewWidgetValidator widgetValidator = new NewWidgetValidator();
+        controller = new WidgetStoreController(widgetService, widgetValidator);
     }
 
     @Test
@@ -100,7 +109,7 @@ public class WidgetStoreControllerTest {
         Model model = new ExtendedModelMap();
 
         String searchTerm = "gAdGet";
-        
+
         int offset = 0;
         int pagesize = 10;
         int totalResults = 2;
@@ -115,14 +124,77 @@ public class WidgetStoreControllerTest {
                 .andReturn(result);
         replay(widgetService);
 
-        String view = controller.viewSearchResult(model,REFERRER_ID, searchTerm, offset);
+        String view = controller.viewSearchResult(model, REFERRER_ID, searchTerm, offset);
         verify(widgetService);
 
         assertEquals(ViewNames.STORE, view);
-        final Map<String,Object> modelMap = model.asMap();
+        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));
     }
+
+    @Test
+    public void startAddWidget() {
+        final Model model = new ExtendedModelMap();
+        final String view = controller.viewAddWidgetForm(model);
+        assertEquals("View for add widget form", ViewNames.ADD_WIDGET_FORM, view);
+        final Widget widget = (Widget) model.asMap().get(ModelKeys.WIDGET);
+        assertNotNull("New widget in Model", widget);
+    }
+
+    @Test
+    public void doAddWidget() {
+        final String widgetUrl = "http://example.com/newwidget.xml";
+        final Model model = new ExtendedModelMap();
+        final Widget widget = new Widget();
+        widget.setTitle("Widget title");
+        widget.setUrl(widgetUrl);
+        widget.setType("OpenSocial");
+        final BindingResult errors = new BeanPropertyBindingResult(widget, "widget");
+
+        expect(widgetService.registerNewWidget(widget)).andReturn(widget);
+        replay(widgetService);
+        String view = controller.viewAddWidgetResult(widget, errors, model);
+        verify(widgetService);
+
+        assertEquals(ViewNames.WIDGET, view);
+        assertFalse("Valid widget data", errors.hasErrors());
+        final Widget fromModel = (Widget) model.asMap().get(ModelKeys.WIDGET);
+        assertEquals(widget, fromModel);
+        assertEquals("New widget has state preview", WidgetStatus.PREVIEW, fromModel.getWidgetStatus());
+    }
+
+    @Test
+    public void doAddWidget_existing() {
+        final String widgetUrl = "http://example.com/existingwidget.xml";
+        final Model model = new ExtendedModelMap();
+        final Widget widget = new Widget();
+        widget.setTitle("Widget title");
+        widget.setUrl(widgetUrl);
+        widget.setType("OpenSocial");
+        final BindingResult errors = new BeanPropertyBindingResult(widget, "widget");
+
+        expect(widgetService.registerNewWidget(widget)).andReturn(null);
+        replay(widgetService);
+        String view = controller.viewAddWidgetResult(widget, errors, model);
+        verify(widgetService);
+
+        assertEquals(ViewNames.ADD_WIDGET_FORM, view);
+        assertTrue("Valid widget data", errors.hasErrors());
+        assertNotNull(model.asMap().get(ModelKeys.WIDGET));
+    }
+
+    @Test
+    public void doAddWidget_invalid() {
+        final Widget widget = new Widget();
+        widget.setTitle("Not enough data");
+        final Model model = new ExtendedModelMap();
+        final BindingResult errors = new BeanPropertyBindingResult(widget, "widget");
+        String view = controller.viewAddWidgetResult(widget, errors, model);
+        assertTrue("Invalid widget data", errors.hasErrors());
+        assertEquals(ViewNames.ADD_WIDGET_FORM, view);
+        assertEquals(widget, model.asMap().get(ModelKeys.WIDGET));
+    }
 }

Added: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/validator/NewWidgetValidatorTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/validator/NewWidgetValidatorTest.java?rev=1161066&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/validator/NewWidgetValidatorTest.java (added)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/validator/NewWidgetValidatorTest.java Wed Aug 24 12:25:29 2011
@@ -0,0 +1,93 @@
+/*
+ * 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.web.validator;
+
+import org.apache.rave.portal.model.Widget;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.validation.BindException;
+import org.springframework.validation.Errors;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * Test class for {@link NewWidgetValidator}
+ */
+public class NewWidgetValidatorTest {
+
+    private static final String VALID_TITLE = "My widget";
+    private static final String VALID_URL = "http://example.com/widget.xml";
+    private static final String VALID_TYPE = "OpenSocial";
+    private static final String WIDGET = "widget";
+
+    private NewWidgetValidator newWidgetValidator;
+
+    @Test
+    public void testSupports() throws Exception {
+        assertTrue("Supports org.apache.rave.portal.model.Widget", newWidgetValidator.supports(Widget.class));
+    }
+
+    @Test
+    public void testValidateValidFormData() throws Exception {
+        Widget widget = new Widget();
+        widget.setTitle(VALID_TITLE);
+        widget.setUrl(VALID_URL);
+        widget.setType(VALID_TYPE);
+        Errors errors = new BindException(widget, WIDGET);
+
+        newWidgetValidator.validate(widget, errors);
+        assertFalse("No validation errors", errors.hasErrors());
+    }
+
+    @Test
+    public void testValidationFailsOnEmptyValues() {
+        Widget widget = new Widget();
+        Errors errors = new BindException(widget, WIDGET);
+
+        newWidgetValidator.validate(widget, errors);
+
+        assertEquals(3, errors.getErrorCount());
+    }
+
+    @Test
+    public void testValidationFailsOnInvalidUrl() {
+        Widget widget = new Widget();
+        widget.setTitle(VALID_TITLE);
+        widget.setType(VALID_TYPE);
+        widget.setUrl("http:/this.is/invalid?url=true&reject=true");
+        widget.setScreenshotUrl("https://///invalid/screenshot");
+        widget.setThumbnailUrl("thumbnail");
+        Errors errors = new BindException(widget, WIDGET);
+
+        newWidgetValidator.validate(widget, errors);
+        assertEquals(3, errors.getErrorCount());
+        assertNotNull("Field error on url", errors.getFieldError("url"));
+        assertNotNull("Field error on screenshot url", errors.getFieldError("screenshotUrl"));
+        assertNotNull("Field error on thumbnail url", errors.getFieldError("thumbnailUrl"));
+    }
+
+    @Before
+    public void setup() {
+        newWidgetValidator = new NewWidgetValidator();
+    }
+}

Modified: incubator/rave/trunk/rave-portal/src/test/resources/portal-test-applicationContext.xml
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/resources/portal-test-applicationContext.xml?rev=1161066&r1=1161065&r2=1161066&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/resources/portal-test-applicationContext.xml (original)
+++ incubator/rave/trunk/rave-portal/src/test/resources/portal-test-applicationContext.xml Wed Aug 24 12:25:29 2011
@@ -109,6 +109,7 @@
     <!-- Validators -->
     <bean id="newAccountValidator" class="org.apache.rave.portal.web.validator.NewAccountValidator"/>
     <bean id="userProfileValidator" class="org.apache.rave.portal.web.validator.UserProfileValidator"/>
+    <bean id="newWidgetValidator" class="org.apache.rave.portal.web.validator.NewWidgetValidator"/>
 
     <!-- Configuring messages.properties file -->
     <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"