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

svn commit: r1158852 - in /incubator/rave/trunk: ./ rave-commons/src/main/java/org/apache/rave/exception/ rave-commons/src/test/java/org/apache/rave/exception/ rave-portal/src/main/java/org/apache/rave/portal/model/ rave-portal/src/main/java/org/apache...

Author: carlucci
Date: Wed Aug 17 18:07:09 2011
New Revision: 1158852

URL: http://svn.apache.org/viewvc?rev=1158852&view=rev
Log:
RAVE-199: Add a new Page to Rave.  This is a sub-task of RAVE-163: Page Management CRUD.  This checkin also includes some common code that was needed - creating an architecture for handling and converting database specific exceptions into Rave exceptions 

Added:
    incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/DuplicateItemException.java
      - copied, changed from r1158276, incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/NotSupportedException.java
    incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/DuplicateItemExceptionTest.java
      - copied, changed from r1158818, incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/NotSupportedExceptionTest.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialect.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/TranslatedH2Exception.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/RepositoryTestUtils.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialectTest.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/TranslatedH2ExceptionTest.java
Modified:
    incubator/rave/trunk/   (props changed)
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Page.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/PageService.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultNewAccountService.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultPageService.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/PageApi.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcOperation.java
    incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcResult.java
    incubator/rave/trunk/rave-portal/src/main/resources/portal.properties
    incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/applicationContext.xml
    incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/home.jsp
    incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_api.js
    incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_forms.js
    incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_layout.js
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/AbstractJpaRepositoryTest.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/PageServiceTest.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/PageApiTest.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcOperationTest.java
    incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcResultTest.java
    incubator/rave/trunk/rave-portal/src/test/javascript/raveApiSpec.js

Propchange: incubator/rave/trunk/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Wed Aug 17 18:07:09 2011
@@ -1,5 +1,4 @@
-target
-
 .project
-
+target
+nbactions.xml
 .settings

Copied: incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/DuplicateItemException.java (from r1158276, incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/NotSupportedException.java)
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/DuplicateItemException.java?p2=incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/DuplicateItemException.java&p1=incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/NotSupportedException.java&r1=1158276&r2=1158852&rev=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/NotSupportedException.java (original)
+++ incubator/rave/trunk/rave-commons/src/main/java/org/apache/rave/exception/DuplicateItemException.java Wed Aug 17 18:07:09 2011
@@ -19,22 +19,21 @@
 
 package org.apache.rave.exception;
 
+import org.springframework.dao.DataAccessException;
+
 /**
- *
+ * Runtime Exception Thrown when a duplicate item is attempted persistence
+ * (such as a unique constraint violation)
+ * 
+ * @author carlucci
  */
-public class NotSupportedException extends RuntimeException{
-    public NotSupportedException() {
-    }
+public class DuplicateItemException extends DataAccessException {
 
-    public NotSupportedException(String s) {
-        super(s);
+    public DuplicateItemException(String msg, Throwable cause) {
+        super(msg, cause);
     }
 
-    public NotSupportedException(String s, Throwable throwable) {
-        super(s, throwable);
-    }
-
-    public NotSupportedException(Throwable throwable) {
-        super(throwable);
-    }
+    public DuplicateItemException(String msg) {
+        super(msg);
+    }    
 }

Copied: incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/DuplicateItemExceptionTest.java (from r1158818, incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/NotSupportedExceptionTest.java)
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/DuplicateItemExceptionTest.java?p2=incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/DuplicateItemExceptionTest.java&p1=incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/NotSupportedExceptionTest.java&r1=1158818&r2=1158852&rev=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/NotSupportedExceptionTest.java (original)
+++ incubator/rave/trunk/rave-commons/src/test/java/org/apache/rave/exception/DuplicateItemExceptionTest.java Wed Aug 17 18:07:09 2011
@@ -26,23 +26,15 @@ import static org.junit.Assert.assertTha
 
 /**
  */
-public class NotSupportedExceptionTest {
+public class DuplicateItemExceptionTest {
 
     private static final String MESSAGE = "MESSAGE";
-
-    @Test
-    public void defaultConstructor() {
-        try {
-            throw new NotSupportedException();
-        } catch (NotSupportedException e) {
-           assertThat(e.getMessage(), is(nullValue()));
-        }
-    }
+   
     @Test
     public void stringConstructor() {
         try {
-            throw new NotSupportedException(MESSAGE);
-        } catch (NotSupportedException e) {
+            throw new DuplicateItemException(MESSAGE);
+        } catch (DuplicateItemException e) {
            assertThat(e.getMessage(), is(equalTo(MESSAGE)));
         }
     }
@@ -50,20 +42,10 @@ public class NotSupportedExceptionTest {
     public void stringAndCauseConstructor() {
         Throwable throwable = new RuntimeException();
         try {
-            throw new NotSupportedException(MESSAGE, throwable);
-        } catch (NotSupportedException e) {
-           assertThat(e.getMessage(), is(equalTo(MESSAGE)));
-           assertThat(e.getCause(), is(sameInstance(throwable)));
-        }
-    }
-    @Test
-    public void causeConstructor() {
-        Throwable throwable = new RuntimeException(MESSAGE);
-        try {
-            throw new NotSupportedException(throwable);
-        } catch (NotSupportedException e) {
-           assertThat(e.getMessage(), is(equalTo(throwable.getClass().getCanonicalName() + ": " + MESSAGE)));
+            throw new DuplicateItemException(MESSAGE, throwable);
+        } catch (DuplicateItemException e) {
+           assertThat(e.getMessage(), is(equalTo(MESSAGE + "; nested exception is java.lang.RuntimeException")));
            assertThat(e.getCause(), is(sameInstance(throwable)));
         }
-    }
+    }    
 }

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Page.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Page.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Page.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/model/Page.java Wed Aug 17 18:07:09 2011
@@ -30,7 +30,7 @@ import java.util.List;
  * become more flexible to enable things like group ownership in the future).
  */
 @Entity
-@Table(name="page")
+@Table(name="page", uniqueConstraints=@UniqueConstraint(columnNames={"owner_id","name"}))
 @SequenceGenerator(name="pageIdSeq", sequenceName = "page_id_seq")
 @NamedQueries({
         @NamedQuery(name = "Page.getByUserId", query="SELECT p FROM Page p WHERE p.owner.id = :userId")
@@ -38,19 +38,19 @@ import java.util.List;
 @Access(AccessType.FIELD)
 public class Page implements BasicEntity, Serializable {
     private static final long serialVersionUID = 1L;
-    
+      
     @Id @Column(name="id")
     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pageIdSeq")
     private Long id;
 
-    @Basic @Column(name="name")
+    @Basic(optional=false) @Column(name="name")
     private String name;
 
     @ManyToOne
     @JoinColumn(name = "owner_id")
     private User owner;
 
-    @Basic @Column(name="render_sequence")
+    @Basic(optional=false) @Column(name="render_sequence")
     private Long renderSequence;
 
     @ManyToOne

Added: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialect.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialect.java?rev=1158852&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialect.java (added)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialect.java Wed Aug 17 18:07:09 2011
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 The Apache Software Foundation.
+ *
+ * Licensed 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.repository.impl;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.rave.exception.DuplicateItemException;
+import org.h2.constant.ErrorCode;
+import org.h2.jdbc.JdbcSQLException;
+import org.springframework.dao.DataAccessException;
+import org.springframework.orm.jpa.vendor.OpenJpaDialect;
+
+/**
+ * This class is an extension of the OpenJpaDialect class used in configuring 
+ * the EntityManagerFactory for our JPA layer.  We needed to override the 
+ * translateExceptionIfPossible method to map the H2 specific database errors
+ * to Rave's application exceptions.  If/when a new Database provider is used instead
+ * of H2 a new class will need to be created for that database implementation.
+ * 
+ * 
+ * @author CARLUCCI
+ */
+public class H2OpenJpaDialect extends OpenJpaDialect {
+    /**
+     * Translates an H2 database error into a Rave application Exception
+     * 
+     * @param re the RuntimeException to translate to a Rave application exception
+     * @return a Rave application exception representing the database error
+     */
+    @Override
+    public DataAccessException translateExceptionIfPossible(RuntimeException re) {        
+        DataAccessException e = null;
+        // first make sure the root exception is actually an org.h2.jdbc.JdbcSQLException
+        if (ExceptionUtils.getRootCause(re) instanceof JdbcSQLException) {
+            JdbcSQLException rootException = (JdbcSQLException)ExceptionUtils.getRootCause(re);
+                        
+            // now translate the H2 specific error codes into Rave's common application Exceptions
+            // add more error codes to the switch statement that should be specifically trapped                         
+            switch(rootException.getErrorCode()) {
+                case ErrorCode.DUPLICATE_KEY_1: {
+                    e = new DuplicateItemException("DUPLICATE_ITEM", rootException);
+                    break;
+                }
+
+                default: {
+                    e = new TranslatedH2Exception(rootException.getErrorCode(), "ERROR", "Unknown Database Error");
+                    break;
+                }
+            }
+        } else {            
+            // we got a RuntimeException that wasn't an org.h2.jdbc.JdbcSQLException
+            e = new TranslatedH2Exception(TranslatedH2Exception.UNKNOWN_ERROR_CODE, "ERROR", "Unknown Runtime Exception");
+        }
+        
+        return e;                
+    }    
+}
\ No newline at end of file

Added: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/TranslatedH2Exception.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/TranslatedH2Exception.java?rev=1158852&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/TranslatedH2Exception.java (added)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/repository/impl/TranslatedH2Exception.java Wed Aug 17 18:07:09 2011
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 The Apache Software Foundation.
+ *
+ * Licensed 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.repository.impl;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Exception class that represents an H2 database exception translated via Spring
+ * 
+ * @author ACARLUCCI
+ */
+public class TranslatedH2Exception extends DataAccessException {
+    private int errorCode;
+    private String error;
+
+    public static final int UNKNOWN_ERROR_CODE = -1;
+    
+    public TranslatedH2Exception(int errorCode, String error, String message) {
+        super(message);
+        this.errorCode = errorCode;
+        this.error = error;
+    }
+
+    public int getErrorCode() {
+        return errorCode;
+    }
+
+    public String getError() {
+        return error;
+    }
+}

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/PageService.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/PageService.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/PageService.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/PageService.java Wed Aug 17 18:07:09 2011
@@ -23,6 +23,7 @@ import org.apache.rave.portal.model.Regi
 import org.apache.rave.portal.model.RegionWidget;
 
 import java.util.List;
+import org.apache.rave.portal.model.User;
 
 public interface PageService {
     /**
@@ -32,7 +33,34 @@ public interface PageService {
      * @return A non null possible empty list of pages for the given user.
      */
     List<Page> getAllPages(long userId);
-
+    
+    /**
+     * Creates a new page with the supplied pageName and pageLayoutCode
+     * 
+     * @param pageName the name of the new page
+     * @param pageLayoutCode the page layout code
+     * @return the new Page object
+     */
+    Page addNewPage(String pageName, String pageLayoutCode);
+    
+    /**
+     * Creates a new default page for the supplied user using the
+     * supplied pageLayoutCode.  This method should be used for new users
+     * who just registered and need to create the default page
+     * 
+     * @param user
+     * @param pageLayoutCode
+     * @return 
+     */
+    Page addNewDefaultPage(User user, String pageLayoutCode);
+    
+    /**
+     * Get the default page name used by Rave
+     * 
+     * @return the name of the default page used by Rave
+     */
+    String getDefaultPageName();
+    
     /**
      * Moves a Region widget's position in a region or across regions
      *
@@ -52,17 +80,11 @@ public interface PageService {
      */
     RegionWidget addWidgetToPage(long page_id, long widget_id);
 
-	 /**
-	  * Deletes the specified widget from the page.
-	  *
-      * @param regionWidgetId the id of the region widget to delete.\
-      * @return the region from which the widget was deleted
-      */
-	 Region removeWidgetFromPage(long regionWidgetId);
-
-	 /**
-	  * Registers a new page.
-	  * @param page the page object to register with the data management system.
-	  */
-	 void registerNewPage(Page page);
+    /**
+     * Deletes the specified widget from the page.
+     *
+     * @param regionWidgetId the id of the region widget to delete.\
+     * @return the region from which the widget was deleted
+     */
+    Region removeWidgetFromPage(long regionWidgetId);
 }
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultNewAccountService.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultNewAccountService.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultNewAccountService.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultNewAccountService.java Wed Aug 17 18:07:09 2011
@@ -41,71 +41,48 @@ import org.springframework.stereotype.Se
 @Service
 public class DefaultNewAccountService implements NewAccountService {
 
-	 protected final Logger logger=LoggerFactory.getLogger(getClass());
+    protected final Logger logger=LoggerFactory.getLogger(getClass());
 
     private final UserService userService;
-	 private final PageService pageService;
-	 private final PageLayoutService pageLayoutService;
-	 private final RegionService regionService;
+    private final PageService pageService;
+    private final PageLayoutService pageLayoutService;
+    private final RegionService regionService;
 
+    @Autowired 
+    private SaltSource saltSource;
 
-	 @Autowired 
-	 private SaltSource saltSource;
-
-	 @Autowired 
-	 private PasswordEncoder passwordEncoder;
+    @Autowired 
+    private PasswordEncoder passwordEncoder;
 
     @Autowired
     public DefaultNewAccountService(UserService userService, PageService pageService, PageLayoutService pageLayoutService, RegionService regionService) {
-		  this.userService = userService;
-	 	  this.pageService = pageService;
-	 	  this.pageLayoutService = pageLayoutService;
-	 	  this.regionService = regionService;
+        this.userService = userService;
+        this.pageService = pageService;
+        this.pageLayoutService = pageLayoutService;
+        this.regionService = regionService;
     }
 
-	 @Override
-	 public void createNewAccount(String userName, String password, String userPageLayout) throws Exception {
-         final User existingUser = userService.getUserByUsername(userName);
-         if (existingUser != null) {
+    @Override
+    public void createNewAccount(String userName, String password, String userPageLayout) throws Exception {
+        final User existingUser = userService.getUserByUsername(userName);
+        if (existingUser != null) {
             throw new IllegalArgumentException("A user already exists for username " + userName);
-         }
-         
-         User user=new User();
-		  user.setUsername(userName);
-		  //This assumes we use the username for the salt.  If not, the code below will need to change.
-		  //See also applicationContext-security.xml
-		  String saltedHashedPassword=passwordEncoder.encodePassword(password,saltSource.getSalt(user));
+        }
+        
+        User user=new User();
+        user.setUsername(userName);
+        //This assumes we use the username for the salt.  If not, the code below will need to change.
+        //See also applicationContext-security.xml
+        String saltedHashedPassword=passwordEncoder.encodePassword(password,saltSource.getSalt(user));
 		  logger.debug("Salt Source: {}", saltSource.getSalt(user));
-		  user.setPassword(saltedHashedPassword);
-		  
-		  user.setExpired(false);
-		  user.setLocked(false);
-		  user.setEnabled(true);
-		  userService.registerNewUser(user);
-		  
-		  //Return the newly registered user
-		  User registeredUser=userService.getUserByUsername(user.getUsername());
-		  
-		  //Create a PageLayout object.		  
-		  PageLayout pageLayout=pageLayoutService.getPageLayoutByCode(userPageLayout);
-		  
-		  //Create regions
-		  List<Region> regions=new ArrayList<Region>();
-		  int regionCount;
-		  for (regionCount = 0; regionCount < pageLayout.getNumberOfRegions(); regionCount++) {
-			  Region region = new Region();
-			  regions.add(region);
-		  }
-		  
-		  //Create a Page object and register it.
-		  Page page=new Page();
-		  page.setName("main");
-		  page.setOwner(registeredUser);
-		  page.setPageLayout(pageLayout);
-		  page.setRenderSequence(1L);
-		  page.setRegions(regions);
-		  pageService.registerNewPage(page);
-		  
-	 }
-	 
+        user.setPassword(saltedHashedPassword);
+
+        user.setExpired(false);
+        user.setLocked(false);
+        user.setEnabled(true);
+        userService.registerNewUser(user);
+
+        // Return the newly registered user and create a new default page for them	  
+        pageService.addNewDefaultPage(userService.getUserByUsername(user.getUsername()), userPageLayout);		  
+    }	 
 }
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultPageService.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultPageService.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultPageService.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/service/impl/DefaultPageService.java Wed Aug 17 18:07:09 2011
@@ -19,6 +19,7 @@
 
 package org.apache.rave.portal.service.impl;
 
+import java.util.ArrayList;
 import org.apache.rave.persistence.Repository;
 import org.apache.rave.portal.model.Page;
 import org.apache.rave.portal.model.Region;
@@ -34,6 +35,11 @@ import org.springframework.stereotype.Se
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import org.apache.rave.portal.model.PageLayout;
+import org.apache.rave.portal.model.User;
+import org.apache.rave.portal.repository.PageLayoutRepository;
+import org.apache.rave.portal.service.UserService;
+import org.springframework.beans.factory.annotation.Value;
 
 @Service
 public class DefaultPageService implements PageService {
@@ -41,19 +47,48 @@ public class DefaultPageService implemen
     private final RegionRepository regionRepository;
     private final RegionWidgetRepository regionWidgetRepository;
     private final WidgetRepository widgetRepository;
+    private final PageLayoutRepository pageLayoutRepository;
+    private final UserService userService;    
+    private final String defaultPageName;
 
     @Autowired
-    public DefaultPageService(PageRepository pageRepository, RegionRepository regionRepository, WidgetRepository widgetRepository, RegionWidgetRepository regionWidgetRepository) {
+    public DefaultPageService(PageRepository pageRepository, 
+                              RegionRepository regionRepository, 
+                              WidgetRepository widgetRepository, 
+                              RegionWidgetRepository regionWidgetRepository,
+                              PageLayoutRepository pageLayoutRepository,
+                              UserService userService,
+                              @Value("${portal.page.default_name}") String defaultPageName) {
         this.pageRepository = pageRepository;
         this.regionRepository = regionRepository;
         this.regionWidgetRepository = regionWidgetRepository;
         this.widgetRepository = widgetRepository;
+        this.pageLayoutRepository = pageLayoutRepository;
+        this.userService = userService;
+        this.defaultPageName = defaultPageName;
     }
 
     @Override
     public List<Page> getAllPages(long userId) {
         return pageRepository.getAllPages(userId);
     }
+    
+    @Override
+    @Transactional
+    public Page addNewPage(String pageName, String pageLayoutCode) {                     
+        return addNewPage(userService.getAuthenticatedUser(), pageName, pageLayoutCode);
+    }    
+    
+    @Override
+    @Transactional
+    public Page addNewDefaultPage(User user, String pageLayoutCode) {                       
+        return addNewPage(user, defaultPageName, pageLayoutCode);
+    }       
+    
+    @Override
+    public String getDefaultPageName() {
+        return defaultPageName;
+    }
 
     @Override
     @Transactional
@@ -85,12 +120,6 @@ public class DefaultPageService implemen
         return createWidgetInstance(widget, region, 0);
     }
 
-    @Override
-    @Transactional
-    public void registerNewPage(Page page) {
-        pageRepository.save(page);
-    }
-
     private RegionWidget createWidgetInstance(Widget widget, Region region, int position) {
         RegionWidget regionWidget = new RegionWidget();
         regionWidget.setRenderOrder(position);
@@ -145,4 +174,27 @@ public class DefaultPageService implemen
         throw new IllegalArgumentException("Invalid RegionWidget ID");
     }
 
+    private Page addNewPage(User user, String pageName, String pageLayoutCode) {
+        PageLayout pageLayout = pageLayoutRepository.getByPageLayoutCode(pageLayoutCode);
+        
+        // Create regions
+        List<Region> regions = new ArrayList<Region>();
+        int regionCount;
+        for (regionCount = 0; regionCount < pageLayout.getNumberOfRegions(); regionCount++) {
+            Region region = new Region();
+            regions.add(region);
+        }
+
+        // Create a Page object and register it.
+        long renderSequence = getAllPages(user.getId()).size() + 1;
+        Page page = new Page();
+        page.setName(pageName);       
+        page.setOwner(user);
+        page.setPageLayout(pageLayout);
+        page.setRenderSequence(renderSequence);
+        page.setRegions(regions);        
+        pageRepository.save(page);
+        
+        return page;
+    }
 }
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/PageApi.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/PageApi.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/PageApi.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/PageApi.java Wed Aug 17 18:07:09 2011
@@ -16,9 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package org.apache.rave.portal.web.api.rpc;
 
+import org.apache.rave.portal.model.Page;
 import org.apache.rave.portal.model.Region;
 import org.apache.rave.portal.model.RegionWidget;
 import org.apache.rave.portal.service.PageService;
@@ -35,95 +35,106 @@ import org.springframework.web.bind.anno
 @RequestMapping(value = "/api/rpc/page/*")
 public class PageApi {
 
-	private final PageService pageService;
+    private final PageService pageService;
 
-	@Autowired
-	public PageApi(PageService pageService) {
-		this.pageService = pageService;
-	}
-
-	/**
-	 * Adds a widget to the given page
-	 * 
-	 * @param pageId
-	 *            the ID of the {@link org.apache.rave.portal.model.Page} to add
-	 *            the widget to
-	 * @param widgetId
-	 *            the ID of the {@link org.apache.rave.portal.model.Widget} to
-	 *            add do the page
-	 * @return a {@link RpcOperation} containing the new widget instance (
-	 *         {@link org.apache.rave.portal.model.RegionWidget }) or any errors
-	 *         encountered while performing the RPC operation
-	 */
-	@ResponseBody
-	@RequestMapping(method = RequestMethod.POST, value = "{pageId}/widget/add")
-	public RpcResult<RegionWidget> addWidgetToPage(
-			@PathVariable final long pageId, @RequestParam final long widgetId) {
-
-		return new RpcOperation<RegionWidget>() {
-			@Override
-			public RegionWidget execute() {
-				return pageService.addWidgetToPage(pageId, widgetId);
-			}
-		}.getResult();
-	}
-
-	/**
-	 * Moves a widget to a new location
-	 * <p/>
-	 * Moves can take place within a region, region to region, or between pages
-	 * 
-	 * @param regionWidgetId
-	 *            the ID of the
-	 *            {@link org.apache.rave.portal.model.RegionWidget} to move
-	 * @param newPosition
-	 *            the 0 based index within the region where the RegionWidget
-	 *            will now be located
-	 * @param toRegion
-	 *            the Id of the {@link org.apache.rave.portal.model.Region }
-	 *            where the widget will now be located
-	 * @param fromRegion
-	 *            the Id of the {@link org.apache.rave.portal.model.Region }
-	 *            where the widget is currently located
-	 * @return a {@link RpcOperation} containing the updated widget instance (
-	 *         {@link org.apache.rave.portal.model.RegionWidget }) or any errors
-	 *         encountered while performing the RPC operation
-	 */
-	@ResponseBody
-	@RequestMapping(method = RequestMethod.POST, value = "regionWidget/{regionWidgetId}/move")
-	public RpcResult<RegionWidget> moveWidgetOnPage(
-			@PathVariable final long regionWidgetId,
-			@RequestParam final int newPosition,
-			@RequestParam final long toRegion,
-			@RequestParam final long fromRegion) {
-
-		return new RpcOperation<RegionWidget>() {
-			@Override
-			public RegionWidget execute() {
-				return pageService.moveRegionWidget(regionWidgetId,
-						newPosition, toRegion, fromRegion);
-			}
-		}.getResult();
-	}
-
-	/**
-	 * Deletes a widget
-	 *
-	 * @param regionWidgetId
-	 *            the ID of the {@link org.apache.rave.portal.model.Widget} to
-	 *            delete
-	 * @return an {@link RpcOperation} containing the updated region or any
-	 *         errors encountered.
-	 */
-	@ResponseBody
-    @RequestMapping(method=RequestMethod.POST, value="regionWidget/{regionWidgetId}/delete")
-    public RpcResult<Region> removeWidgetFromPage(@PathVariable final long regionWidgetId) {
-		return new RpcOperation<Region>() {
-			@Override
-			public Region execute() {
-		        return pageService.removeWidgetFromPage(regionWidgetId);
+    @Autowired
+    public PageApi(PageService pageService) {
+        this.pageService = pageService;
+    }
+
+    /**
+     * Adds a widget to the given page
+     * 
+     * @param pageId
+     *            the ID of the {@link org.apache.rave.portal.model.Page} to add
+     *            the widget to
+     * @param widgetId
+     *            the ID of the {@link org.apache.rave.portal.model.Widget} to
+     *            add do the page
+     * @return a {@link RpcOperation} containing the new widget instance (
+     *         {@link org.apache.rave.portal.model.RegionWidget }) or any errors
+     *         encountered while performing the RPC operation
+     */
+    @ResponseBody
+    @RequestMapping(method = RequestMethod.POST, value = "{pageId}/widget/add")
+    public RpcResult<RegionWidget> addWidgetToPage(
+            @PathVariable final long pageId, @RequestParam final long widgetId) {
+
+        return new RpcOperation<RegionWidget>() {
+            @Override
+            public RegionWidget execute() {
+                return pageService.addWidgetToPage(pageId, widgetId);
             }
-		}.getResult();
+        }.getResult();
     }
 
+    /**
+     * Moves a widget to a new location
+     * <p/>
+     * Moves can take place within a region, region to region, or between pages
+     * 
+     * @param regionWidgetId
+     *            the ID of the
+     *            {@link org.apache.rave.portal.model.RegionWidget} to move
+     * @param newPosition
+     *            the 0 based index within the region where the RegionWidget
+     *            will now be located
+     * @param toRegion
+     *            the Id of the {@link org.apache.rave.portal.model.Region }
+     *            where the widget will now be located
+     * @param fromRegion
+     *            the Id of the {@link org.apache.rave.portal.model.Region }
+     *            where the widget is currently located
+     * @return a {@link RpcOperation} containing the updated widget instance (
+     *         {@link org.apache.rave.portal.model.RegionWidget }) or any errors
+     *         encountered while performing the RPC operation
+     */
+    @ResponseBody
+    @RequestMapping(method = RequestMethod.POST, value = "regionWidget/{regionWidgetId}/move")
+    public RpcResult<RegionWidget> moveWidgetOnPage(
+            @PathVariable final long regionWidgetId,
+            @RequestParam final int newPosition,
+            @RequestParam final long toRegion,
+            @RequestParam final long fromRegion) {
+
+        return new RpcOperation<RegionWidget>() {
+            @Override
+            public RegionWidget execute() {
+                return pageService.moveRegionWidget(regionWidgetId,
+                        newPosition, toRegion, fromRegion);
+            }
+        }.getResult();
+    }
+
+    /**
+     * Deletes a widget
+     *
+     * @param regionWidgetId
+     *            the ID of the {@link org.apache.rave.portal.model.Widget} to
+     *            delete
+     * @return an {@link RpcOperation} containing the updated region or any
+     *         errors encountered.
+     */
+    @ResponseBody
+    @RequestMapping(method = RequestMethod.POST, value = "regionWidget/{regionWidgetId}/delete")
+    public RpcResult<Region> removeWidgetFromPage(@PathVariable final long regionWidgetId) {
+        return new RpcOperation<Region>() {
+            @Override
+            public Region execute() {
+                return pageService.removeWidgetFromPage(regionWidgetId);
+            }
+        }.getResult();
+    }
+    
+    @ResponseBody
+    @RequestMapping(method = RequestMethod.POST, value = "add")
+    public RpcResult<Page> addPage(@RequestParam final String pageName,
+                                   @RequestParam final String pageLayoutCode) {
+        return new RpcOperation<Page>() {
+             @Override
+             public Page execute() {
+                 return pageService.addNewPage(pageName, pageLayoutCode);
+             }
+        }.getResult();        
+    }
 }

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcOperation.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcOperation.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcOperation.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcOperation.java Wed Aug 17 18:07:09 2011
@@ -19,6 +19,8 @@
 
 package org.apache.rave.portal.web.api.rpc.model;
 
+import org.apache.rave.exception.DuplicateItemException;
+
 /**
  * Defines an RPC operation that can be executed to return a given result
  */
@@ -36,9 +38,11 @@ public abstract class RpcOperation<T> {
             T subject = execute();
             result = new RpcResult<T>(false, subject);
         } catch (IllegalArgumentException e) {
-            result = new RpcResult<T>(true, e.getMessage(), RpcResult.ErrorCode.INVALID_PARAMS);
+            result = createRpcResultError(e, RpcResult.ErrorCode.INVALID_PARAMS);
+        } catch (DuplicateItemException e) {
+            result = createRpcResultError(e, RpcResult.ErrorCode.DUPLICATE_ITEM);
         } catch (Exception e) {
-            result = new RpcResult<T>(true, e.getMessage(), RpcResult.ErrorCode.INTERNAL_ERROR);
+            result = createRpcResultError(e, RpcResult.ErrorCode.INTERNAL_ERROR);            
         }
         return result;
     }
@@ -49,4 +53,8 @@ public abstract class RpcOperation<T> {
      * @return the result of the RPC operation
      */
     public abstract T execute();
-}
+        
+    private RpcResult<T> createRpcResultError(Exception e, RpcResult.ErrorCode errorCode) {      
+        return new RpcResult<T>(true, e.getMessage(), errorCode);
+    }
+}
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcResult.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcResult.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcResult.java (original)
+++ incubator/rave/trunk/rave-portal/src/main/java/org/apache/rave/portal/web/api/rpc/model/RpcResult.java Wed Aug 17 18:07:09 2011
@@ -30,7 +30,8 @@ public class RpcResult<T> {
     public static enum ErrorCode {
         NO_ERROR,
         INVALID_PARAMS,
-        INTERNAL_ERROR
+        INTERNAL_ERROR,
+        DUPLICATE_ITEM
     }
 
     private boolean error;

Modified: incubator/rave/trunk/rave-portal/src/main/resources/portal.properties
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/resources/portal.properties?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/resources/portal.properties (original)
+++ incubator/rave/trunk/rave-portal/src/main/resources/portal.properties Wed Aug 17 18:07:09 2011
@@ -28,4 +28,7 @@ portal.opensocial_engine.gadget_path=/ga
 
 portal.opensocial_security.encryptionkey=classpath:security_token_encryption_key.txt
 portal.opensocial_security.container=default
-portal.opensocial_security.domain=default
\ No newline at end of file
+portal.opensocial_security.domain=default
+
+# the default page name to create for new users
+portal.page.default_name=Main
\ No newline at end of file

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=1158852&r1=1158851&r2=1158852&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 17 18:07:09 2011
@@ -68,10 +68,14 @@
         <property name="persistenceUnitName" value="ravePersistenceUnit"/>
         <property name="dataSource" ref="dataSource"/>
         <property name="jpaVendorAdapter">
-            <bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter">
-                <property name="showSql" value="true" />
-            </bean>
+            <bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"
+                  p:databasePlatform="org.apache.openjpa.jdbc.sql.H2Dictionary"
+                  p:database="H2"
+                  p:showSql="true"/>
         </property>
+        <property name="jpaDialect">
+            <bean class="org.apache.rave.portal.repository.impl.H2OpenJpaDialect"/>
+        </property>      
         <property name="jpaPropertyMap">
             <map>
                 <entry key="openjpa.Log" value="DefaultLevel=WARN, Runtime=INFO, Tool=INFO, SQL=TRACE"/>
@@ -80,7 +84,7 @@
             </map>
         </property>
     </bean>
-
+    
     <!-- A RestTemplate instance that can be used to call a web service which expects Content-Type and Accept headers of
     application/json with a pre-built string of JSON data. -->
     <bean id="jsonStringCompatibleRestTemplate" class="org.springframework.web.client.RestTemplate">
@@ -104,17 +108,17 @@
         <property name="engineGadgetPath" value="${portal.opensocial_engine.gadget_path}"/>
     </bean>
 
-	 <!-- Validators -->
-	<bean id="newAccountValidator" class="org.apache.rave.portal.web.validator.NewAccountValidator"/>
-	<bean id="userProfileValidator" class="org.apache.rave.portal.web.validator.UserProfileValidator"/>
+    <!-- Validators -->
+    <bean id="newAccountValidator" class="org.apache.rave.portal.web.validator.NewAccountValidator"/>
+    <bean id="userProfileValidator" class="org.apache.rave.portal.web.validator.UserProfileValidator"/>
 	
-	<!-- Configuring messages.properties file --> 
-	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basename="messages" />
+    <!-- Configuring messages.properties file --> 
+    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basename="messages" />
 
-	<!-- Password encoding and salting-->
-	<bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" id="passwordEncoder"/>
-	<bean class="org.springframework.security.authentication.dao.ReflectionSaltSource" id="saltSource">
-	  <property name="userPropertyToUse" value="username"/>
-	</bean>
+    <!-- Password encoding and salting-->
+    <bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" id="passwordEncoder"/>
+    <bean class="org.springframework.security.authentication.dao.ReflectionSaltSource" id="saltSource">
+        <property name="userPropertyToUse" value="username"/>
+    </bean>
 
 </beans>
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/home.jsp
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/home.jsp?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/home.jsp (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/WEB-INF/views/home.jsp Wed Aug 17 18:07:09 2011
@@ -40,25 +40,26 @@
         <h1>Hello ${defaultPage.owner.username}, welcome to Rave!</h1>
     </div>
     <div id="dialog" title="Tab data" class="dialog">
-		<form>
-			<fieldset class="ui-helper-reset">
-				<label for="tab_title">Title</label>
-				<input type="text" name="tab_title" id="tab_title" value="" class="ui-widget-content ui-corner-all" />
-				<label for="pageLayoutField">Select Page Layout:</label>
-            	<select>
-            		<option value="columns_1" id="columns_1_id">One Column</option>
-            		<option value="columns_2" id="columns_2_id" selected="selected">Two Columns</option>
-            		<option value="columns_2wn" id="columns_2wn_id">Two Columns (wide/narrow)</option>
-            		<option value="columns_3" id="columns_3_id">Three Columns</option>
-            		<option value="columns_3nwn" id="columns_3nwn_id">Three Columns (narrow/wide/narrow)</option>
-            		<option value="columns_4" id="columns_4_id">Four Columns</option>
-            		<option value="columns_3nwn_1_bottom" id="columns_3nwn_1_bottom">Four Columns (narrow/wide/narrow/bottom)</option>
-            	</select>
-			</fieldset>
-		</form>
-	</div>
-	<button id="add_tab">Add Tab</button>
-      <div id="tabs" class="rave-ui-tabs">
+        <form id="pageForm">
+            <div id="pageFormErrors" class="error"></div>
+            <fieldset class="ui-helper-reset">
+                <label for="tab_title">Title</label>
+                <input type="text" name="tab_title" id="tab_title" value="" class="required ui-widget-content ui-corner-all" />
+                <label for="pageLayoutField">Select Page Layout:</label>
+                <select name="pageLayout" id="pageLayout">
+                    <option value="columns_1" id="columns_1_id">One Column</option>
+                    <option value="columns_2" id="columns_2_id" selected="selected">Two Columns</option>
+                    <option value="columns_2wn" id="columns_2wn_id">Two Columns (wide/narrow)</option>
+                    <option value="columns_3" id="columns_3_id">Three Columns</option>
+                    <option value="columns_3nwn" id="columns_3nwn_id">Three Columns (narrow/wide/narrow)</option>
+                    <option value="columns_4" id="columns_4_id">Four Columns</option>
+                    <option value="columns_3nwn_1_bottom" id="columns_3nwn_1_bottom">Four Columns (narrow/wide/narrow/bottom)</option>
+                </select>
+            </fieldset>
+        </form>
+    </div>
+    <button id="add_tab">Add Tab</button>
+    <div id="tabs" class="rave-ui-tabs">
     <ul class="rave-ui-tabs ui-tabs-nav">
 	<c:forEach var="page" items="${pages}">
     		<li>
@@ -102,6 +103,7 @@
     <script src="//cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
     <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.min.js"></script>
     <script src="//ajax.aspnetcdn.com/ajax/jquery.ui/1.8.13/jquery-ui.min.js"></script>
+    <script src="//ajax.aspnetcdn.com/ajax/jquery.validate/1.8.1/jquery.validate.min.js"></script>
     <script src="${opensocial_engine_url}/js/container.js?c=1&container=default&debug=1"></script>
     <script src="<spring:url value="/script/rave.js"/>"></script>
     <script src="<spring:url value="/script/rave_api.js"/>"></script>
@@ -132,5 +134,8 @@
         $(function() {
     		$( "#tabs" ).tabs();
     	});
+        
+        // initialize the page form validator
+        rave.forms.validateUserProfileForm();
     </script>
 </rave:rave_generic_page>

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_api.js
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_api.js?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_api.js (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_api.js Wed Aug 17 18:07:09 2011
@@ -130,6 +130,28 @@ rave.api = rave.api || (function() {
                     }
                 }).error(handleError);
         }
+        
+        function addPage(args) {
+            $.post(rave.getContext() + path + "page/add",
+                {
+                    pageName: args.pageName,
+                    pageLayoutCode: args.pageLayoutCode
+                },
+                function(result) {
+                    if (result.error) {
+                        // check to see if a duplicate page name error occured
+                        if (result.errorCode == 'DUPLICATE_ITEM') {                        
+                            $("#pageFormErrors").html("A page with that name already exists");
+                        } else {                        
+                            handleRpcError(result);
+                        }
+                    } else {
+                        if (typeof args.successCallback == 'function') {
+                            args.successCallback();
+                        }
+                    }
+                }).error(handleError);
+        }
 
         //TODO: Create a more robust error handling system and interrogation of RPC results
         function handleRpcError(rpcResult) {
@@ -143,14 +165,15 @@ rave.api = rave.api || (function() {
                 case "INTERNAL_ERROR":
                     alert("Rave attempted to update the server with your recent changes, " +
                         " but the server encountered an internal error.");
-                    break;
+                    break;                
             }
         }
 
         return {
             moveWidget : moveWidgetOnPage,
             addWidgetToPage : addWidgetToPage,
-            removeWidget : deleteWidgetOnPage
+            removeWidget : deleteWidgetOnPage,
+            addPage: addPage
         };
 
     })();

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_forms.js
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_forms.js?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_forms.js (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_forms.js Wed Aug 17 18:07:09 2011
@@ -65,9 +65,20 @@ rave.forms = rave.forms || (function() {
             }
         });
     }
+    
+    function validatePageForm() {
+        $("#pageForm").validate({
+            rules: {
+                tab_title : {
+                    required: true
+                }
+            }
+        });
+    }
 
     return {
         validateNewAccountForm : validateNewAccountForm,
-		  validateUserProfileForm: validateUserProfileForm
+	validateUserProfileForm: validateUserProfileForm,
+        validatePageForm: validatePageForm
     };
 })();

Modified: incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_layout.js
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_layout.js?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_layout.js (original)
+++ incubator/rave/trunk/rave-portal/src/main/webapp/script/rave_layout.js Wed Aug 17 18:07:09 2011
@@ -17,64 +17,79 @@
  * under the License.
  */
 $(function() {
-    		var $tab_title_input = $( "#tab_title"),
-    			$tab_content_input = $( "#tab_content" );
-    		var tab_counter = 2;
-    		// tabs init with a custom tab template and an "add" callback filling in the content
-    		var $tabs = $( "#tabs").tabs({
-    			tabTemplate: "<li><a href='#{href}'>#{label}</a> <span class='ui-icon ui-icon-close'>Remove Tab</span></li>",
-    			add: function( event, ui ) {
-    				var tab_content = $tab_content_input.val() || "Tab " + tab_counter + " content.";
-    				$( ui.panel ).append( "<p>" + tab_content + "</p>" );
-    			}
-    		});
-
-    		// modal dialog init: custom buttons and a "close" callback reseting the form inside
-    		var $dialog = $( "#dialog" ).dialog({
-    			autoOpen: false,
-    			modal: true,
-    			buttons: {
-    				Add: function() {
-    					addTab();
-    					$( this ).dialog( "close" );
-    				},
-    				Cancel: function() {
-    					$( this ).dialog( "close" );
-    				}
-    			},
-    			open: function() {
-    				$tab_title_input.focus();
-    			},
-    			close: function() {
-    				$form[ 0 ].reset();
-    			}
-    		});
-
-    		// addTab form: calls addTab function on submit and closes the dialog
-    		var $form = $( "form", $dialog ).submit(function() {
-    			addTab();
-    			$dialog.dialog( "close" );
-    			return false;
-    		});
-
-    		// actual addTab function: adds new tab using the title input from the form above
-    		function addTab() {
-    			var tab_title = $tab_title_input.val() || "Tab " + tab_counter;
-    			$tabs.tabs( "add", "#tabs-" + tab_counter, tab_title );
-    			tab_counter++;
-    		}
-
-    		// addTab button: just opens the dialog
-    		$( "#add_tab" )
-    			.button()
-    			.click(function() {
-    				$dialog.dialog( "open" );
-    			});
-
-    		// close icon: removing the tab on click
-    		// note: closable tabs gonna be an option in the future - see http://dev.jqueryui.com/ticket/3924
-    		$( "#tabs span.ui-icon-close" ).live( "click", function() {
-    			var index = $( "li", $tabs ).index( $( this ).parent() );
-    			$tabs.tabs( "remove", index );
-    		});
+    var $tab_title_input = $( "#tab_title"),
+        $page_layout_input = $("#pageLayout"),
+        $tab_content_input = $( "#tab_content" );
+        
+    var tab_counter = 2;
+    // tabs init with a custom tab template and an "add" callback filling in the content
+    var $tabs = $( "#tabs").tabs({
+            tabTemplate: "<li><a href='#{href}'>#{label}</a> <span class='ui-icon ui-icon-close'>Remove Tab</span></li>",
+            add: function( event, ui ) {
+                    var tab_content = $tab_content_input.val() || "Tab " + tab_counter + " content.";
+                    $( ui.panel ).append( "<p>" + tab_content + "</p>" );
+            }
+    });
+
+    // modal dialog init: custom buttons and a "close" callback reseting the form inside
+    var $dialog = $( "#dialog" ).dialog({
+            autoOpen: false,
+            modal: true,
+            buttons: {
+                    Add: function() {
+                            addPage();                            
+                    },
+                    Cancel: function() {
+                            $( this ).dialog( "close" );
+                    }
+            },
+            open: function() {
+                    $tab_title_input.focus();
+            },
+            close: function() {
+                    $form[ 0 ].reset();
+                    $("#pageFormErrors").html("");
+            }
+    });
+
+    // define the form object    
+    var $form = $("#pageForm", $dialog);  
+
+    function addPage() {
+        // if the form has passed validation submit the request
+        if ($("#pageForm").valid()) {        
+            var newPageTitle = $tab_title_input.val();
+            var newPageLayoutCode = $page_layout_input.val();
+
+            // send the rpc request to create the new page
+            rave.api.rpc.addPage({pageName: newPageTitle, 
+                                  pageLayoutCode: newPageLayoutCode,
+                                  successCallback: function() { addPageCallback(newPageTitle); } 
+                                 });      
+        }
+    }
+        
+    function addPageCallback(newPageTitle) {
+        // add the new page tab to the list of pages
+        // TODO - in the future this should be changed to redirect to the new page after creating it
+        var pageTitle = newPageTitle;
+        $tabs.tabs("add", "#tabs-" + tab_counter, pageTitle);
+        tab_counter++;
+        
+        $dialog.dialog( "close" );
+    }
+
+    // addTab button: just opens the dialog
+    $( "#add_tab" )
+            .button()
+            .click(function() {
+                    $dialog.dialog( "open" );
+            });
+
+    // close icon: removing the tab on click
+    // note: closable tabs gonna be an option in the future - see http://dev.jqueryui.com/ticket/3924
+    $( "#tabs span.ui-icon-close" ).live( "click", function() {
+            var index = $( "li", $tabs ).index( $( this ).parent() );
+            $tabs.tabs( "remove", index );
+    });
 });
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/AbstractJpaRepositoryTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/AbstractJpaRepositoryTest.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/AbstractJpaRepositoryTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/AbstractJpaRepositoryTest.java Wed Aug 17 18:07:09 2011
@@ -79,9 +79,10 @@ public class AbstractJpaRepositoryTest {
 
     @Test
     @Rollback(true)
-    public void save_newEntity() {
+    public void save_newEntity() throws Exception {
         for (Repository repository : repositories) {
-            BasicEntity entity = constructNewEntityForRepository(repository);
+            BasicEntity entity = constructNewEntityForRepository(repository);            
+            RepositoryTestUtils.populateAllRequiredFieldsInEntity(sharedManager, entity);            
             BasicEntity saved = (BasicEntity)repository.save(entity);
             sharedManager.flush();
             assertThat(saved, is(sameInstance(entity)));
@@ -121,5 +122,5 @@ public class AbstractJpaRepositoryTest {
             throw new RuntimeException(e);
         }
     }
-
+    
 }

Added: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/RepositoryTestUtils.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/RepositoryTestUtils.java?rev=1158852&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/RepositoryTestUtils.java (added)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/RepositoryTestUtils.java Wed Aug 17 18:07:09 2011
@@ -0,0 +1,88 @@
+/*
+ * 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.repository;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import org.apache.openjpa.meta.FieldMetaData;
+import org.apache.openjpa.persistence.JPAFacadeHelper;
+import org.apache.rave.persistence.BasicEntity;
+
+/**
+ *
+ * @author ACARLUCCI
+ */
+public class RepositoryTestUtils {
+    
+    /**
+     * Utility function for populating all required fields in model entities
+     * by using reflection and JPA entity meta data inspection to determine
+     * which fields are NOT NULL allowable.
+     * 
+     * @param entityManager
+     * @param entity
+     * @throws Exception 
+     */
+    public static void populateAllRequiredFieldsInEntity(EntityManager entityManager, BasicEntity entity) throws Exception {
+        // loop over all fields in entity and determine which ones are 
+        // "required", (optional=false) in JPA config terms, and set a value
+        // to those fields            
+        FieldMetaData[] declaredFields = JPAFacadeHelper.getMetaData(entityManager, entity.getClass()).getDeclaredFields();
+        // for each field int the entity...
+        for (FieldMetaData fieldMetaData : declaredFields) {
+            // if the field is declared optional=false...
+            if (fieldMetaData.getFieldMetaData().getNullValue() == FieldMetaData.NULL_EXCEPTION) {                    
+                String fieldName = fieldMetaData.getName();                    
+                // using reflection obtain the getter and setter methods for this field...
+                for (Method method : entity.getClass().getDeclaredMethods()) {
+                    String methodPrefix = method.getName().substring(0,3);
+                    String methodName = method.getName().substring(3);
+                    if("get".equals(methodPrefix) && fieldName.equalsIgnoreCase(methodName)) {
+                        String setterMethodName = "set" + methodName;                           
+                        Method setterMethod = entity.getClass().getMethod(setterMethodName, method.getReturnType());                            
+                        invokeEntitySetterMethodBasedOnAvailableConstructor(entity, method, setterMethod);                 
+                    }
+                }                                        
+            }
+        }
+    }
+    
+    private static void invokeEntitySetterMethodBasedOnAvailableConstructor(BasicEntity entity, Method getterMethod, Method setterMethod) throws Exception {        
+        // first see if there is a simple no-arg default constructor for this field type
+        Constructor<?> defaultConstructor = null;
+        try {
+            defaultConstructor = getterMethod.getReturnType().getConstructor();
+        } catch (NoSuchMethodException e) { }
+
+        if (defaultConstructor != null) {                                
+            setterMethod.invoke(entity, defaultConstructor.newInstance());    
+        } else {
+            // check to see what the type is and invoke a constructor
+            // new classes might need to be added here in the future 
+            // based on what fields are required in the models
+            if (getterMethod.getReturnType().equals(Long.class)) {
+                setterMethod.invoke(entity, new Long(1L)); 
+            } else if (getterMethod.getReturnType().equals(Integer.class)) {
+                setterMethod.invoke(entity, new Integer(1)); 
+            }
+        }           
+    }    
+}

Added: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialectTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialectTest.java?rev=1158852&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialectTest.java (added)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/H2OpenJpaDialectTest.java Wed Aug 17 18:07:09 2011
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 The Apache Software Foundation.
+ *
+ * Licensed 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.repository.impl;
+
+import org.apache.rave.exception.DuplicateItemException;
+import org.h2.jdbc.JdbcSQLException;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.h2.constant.ErrorCode;
+import org.springframework.dao.DataAccessException;
+
+/**
+ *
+ * @author ACARLUCCI
+ */
+public class H2OpenJpaDialectTest {
+    private H2OpenJpaDialect dialect;
+    
+    @Before
+    public void setUp() {
+        dialect = new H2OpenJpaDialect();
+    }
+  
+    /**
+     * Test of translateExceptionIfPossible method, of class H2OpenJpaDialect.
+     */
+    @Test
+    public void testTranslateExceptionIfPossible_uniqueContstraintViolation() {                 
+        JdbcSQLException jdbcEx = new JdbcSQLException("message", "sql statement", "state", ErrorCode.DUPLICATE_KEY_1, null, "stacktrace");
+        RuntimeException re = new RuntimeException("dummy runtime exception", jdbcEx);     
+                       
+        assertThat(dialect.translateExceptionIfPossible(re), is(DuplicateItemException.class));
+    }
+    
+    @Test
+    public void testTranslateExceptionIfPossible_unknownJdbcSQLExceptionError() {                 
+        JdbcSQLException jdbcEx = new JdbcSQLException("message", "sql statement", "state", ErrorCode.CANNOT_DROP_CURRENT_USER, null, "stacktrace");
+        RuntimeException re = new RuntimeException("dummy runtime exception", jdbcEx);     
+        
+        TranslatedH2Exception translatedException = (TranslatedH2Exception) dialect.translateExceptionIfPossible(re);             
+        assertThat(translatedException.getErrorCode(), is(ErrorCode.CANNOT_DROP_CURRENT_USER));        
+    }
+    
+    @Test
+    public void testTranslateExceptionIfPossible_unknownRuntimeError() {                         
+        RuntimeException re = new RuntimeException("dummy runtime exception", new NullPointerException("bad"));     
+        
+        TranslatedH2Exception translatedException = (TranslatedH2Exception) dialect.translateExceptionIfPossible(re);             
+        assertThat(translatedException.getErrorCode(), is(TranslatedH2Exception.UNKNOWN_ERROR_CODE));        
+    }    
+}

Added: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/TranslatedH2ExceptionTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/TranslatedH2ExceptionTest.java?rev=1158852&view=auto
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/TranslatedH2ExceptionTest.java (added)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/repository/impl/TranslatedH2ExceptionTest.java Wed Aug 17 18:07:09 2011
@@ -0,0 +1,63 @@
+/*
+ * 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.repository.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+
+/**
+ *
+ * @author CARLUCCI
+ */
+public class TranslatedH2ExceptionTest {       
+    private final int ERROR_CODE = 123;
+    private final String ERROR = "Dummy Error";
+    private final String MESSAGE = "H2 specific error message";
+    
+    private TranslatedH2Exception e;
+    
+    @Before
+    public void setup() {
+        e = new TranslatedH2Exception(ERROR_CODE, ERROR, MESSAGE);
+    }
+        
+    /**
+     * Test of getErrorCode method, of class TranslatedH2Exception.
+     */
+    @Test
+    public void testGetErrorCode() {
+        assertThat(e.getErrorCode(), is(ERROR_CODE));
+    }
+
+    /**
+     * Test of getError method, of class TranslatedH2Exception.
+     */
+    @Test
+    public void testGetError() {
+        assertThat(e.getError(), is(ERROR));
+    }
+    
+    @Test
+    public void testGetMessage() {
+        assertThat(e.getMessage(), is(MESSAGE));
+    }    
+}

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/PageServiceTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/PageServiceTest.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/PageServiceTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/service/PageServiceTest.java Wed Aug 17 18:07:09 2011
@@ -19,6 +19,9 @@
 
 package org.apache.rave.portal.service;
 
+import org.apache.rave.portal.model.PageLayout;
+import org.apache.rave.portal.model.User;
+import org.apache.rave.portal.repository.PageLayoutRepository;
 import org.apache.rave.portal.model.Page;
 import org.apache.rave.portal.model.Region;
 import org.apache.rave.portal.model.RegionWidget;
@@ -46,13 +49,19 @@ public class PageServiceTest {
     private RegionRepository regionRepository;
     private WidgetRepository widgetRepository;
     private RegionWidgetRepository regionWidgetRepository;
+    private PageLayoutRepository pageLayoutRepository;
+    private UserService userService;
 
     private static final long REGION_WIDGET_ID = 5L;
     private static final long TO_REGION_ID = 1L;
     private static final long FROM_REGION_ID = 2L;
+    private static final String PAGE_LAYOUT_CODE = "layout1";
     private Region targetRegion;
     private Region originalRegion;
-    private Widget widget;
+    private Widget validWidget;
+    private User user;
+    private PageLayout pageLayout;
+    private String defaultPageName = "Main";
 
     @Before
     public void setup() {
@@ -61,23 +70,34 @@ public class PageServiceTest {
         regionRepository = createNiceMock(RegionRepository.class);
         widgetRepository = createNiceMock(WidgetRepository.class);
         regionWidgetRepository = createNiceMock(RegionWidgetRepository.class);
-        pageService = new DefaultPageService(pageRepository, regionRepository, widgetRepository, regionWidgetRepository);
+        pageLayoutRepository = createMock(PageLayoutRepository.class);
+        userService = createMock(UserService.class);
+        pageService = new DefaultPageService(pageRepository, regionRepository, widgetRepository, regionWidgetRepository, pageLayoutRepository, userService, defaultPageName);
 
-        widget = new Widget(1L, "http://dummy.apache.org/widgets/widget.xml");
+        validWidget = new Widget(1L, "http://dummy.apache.org/widgets/widget.xml");
         
         targetRegion = new Region();
         targetRegion.setId(2L);
         targetRegion.setRegionWidgets(new ArrayList<RegionWidget>());
-        targetRegion.getRegionWidgets().add(new RegionWidget(1L, widget, targetRegion, 0));
-        targetRegion.getRegionWidgets().add(new RegionWidget(2L, widget, targetRegion, 1));
-        targetRegion.getRegionWidgets().add(new RegionWidget(3L, widget, targetRegion, 2));
+        targetRegion.getRegionWidgets().add(new RegionWidget(1L, validWidget, targetRegion, 0));
+        targetRegion.getRegionWidgets().add(new RegionWidget(2L, validWidget, targetRegion, 1));
+        targetRegion.getRegionWidgets().add(new RegionWidget(3L, validWidget, targetRegion, 2));
 
         originalRegion = new Region();
         originalRegion.setId(1L);
         originalRegion.setRegionWidgets(new ArrayList<RegionWidget>());
-        originalRegion.getRegionWidgets().add(new RegionWidget(4L, widget, targetRegion, 0));
-        originalRegion.getRegionWidgets().add(new RegionWidget(5L, widget, targetRegion, 1));
-        originalRegion.getRegionWidgets().add(new RegionWidget(6L, widget, targetRegion, 2));
+        originalRegion.getRegionWidgets().add(new RegionWidget(4L, validWidget, targetRegion, 0));
+        originalRegion.getRegionWidgets().add(new RegionWidget(5L, validWidget, targetRegion, 1));
+        originalRegion.getRegionWidgets().add(new RegionWidget(6L, validWidget, targetRegion, 2));
+        
+        user = new User();
+        user.setId(1L);
+        user.setUsername("acarlucci"); 
+        
+        pageLayout = new PageLayout();
+        pageLayout.setId(1L);
+        pageLayout.setCode(PAGE_LAYOUT_CODE);
+        pageLayout.setNumberOfRegions(3L);
     }
 
     @Test
@@ -92,6 +112,99 @@ public class PageServiceTest {
     }
 
     @Test
+    public void createNewPage_noExistingPages() {
+        final String PAGE_NAME = "my new page";
+        final Long EXPECTED_RENDER_SEQUENCE = 1L;
+                      
+        Page expectedPage = new Page();
+        expectedPage.setName(PAGE_NAME);       
+        expectedPage.setOwner(user);
+        expectedPage.setPageLayout(pageLayout);
+        expectedPage.setRenderSequence(EXPECTED_RENDER_SEQUENCE);
+        expectedPage.setRegions(createEmptyRegionList(pageLayout.getNumberOfRegions()));    
+                
+        expect(userService.getAuthenticatedUser()).andReturn(user);
+        expect(pageLayoutRepository.getByPageLayoutCode(PAGE_LAYOUT_CODE)).andReturn(pageLayout);
+        expect(pageRepository.save(expectedPage)).andReturn(expectedPage);
+        expect(pageRepository.getAllPages(user.getId())).andReturn(new ArrayList<Page>());
+        replay(userService);
+        replay(pageLayoutRepository);
+        replay(pageRepository);             
+
+        Page newPage = pageService.addNewPage(PAGE_NAME, PAGE_LAYOUT_CODE);                
+        assertThat(newPage.getRenderSequence(), is(EXPECTED_RENDER_SEQUENCE));
+        assertThat(newPage.getName(), is(PAGE_NAME));
+        assertThat(newPage.getRegions().size(), is(pageLayout.getNumberOfRegions().intValue()));
+        
+        verify(userService);
+        verify(pageLayoutRepository);
+        verify(pageRepository);
+    }
+    
+    @Test
+    public void createNewPage_existingPages() {
+        final String PAGE_NAME = "my new page";
+        final Long EXPECTED_RENDER_SEQUENCE = 2L;
+        List<Page> existingPages = new ArrayList<Page>();
+        existingPages.add(new Page());
+                      
+        Page expectedPage = new Page();
+        expectedPage.setName(PAGE_NAME);       
+        expectedPage.setOwner(user);
+        expectedPage.setPageLayout(pageLayout);
+        expectedPage.setRenderSequence(EXPECTED_RENDER_SEQUENCE);
+        expectedPage.setRegions(createEmptyRegionList(pageLayout.getNumberOfRegions()));    
+                
+        expect(userService.getAuthenticatedUser()).andReturn(user);
+        expect(pageLayoutRepository.getByPageLayoutCode(PAGE_LAYOUT_CODE)).andReturn(pageLayout);
+        expect(pageRepository.save(expectedPage)).andReturn(expectedPage);
+        expect(pageRepository.getAllPages(user.getId())).andReturn(existingPages);
+        replay(userService);
+        replay(pageLayoutRepository);
+        replay(pageRepository);             
+
+        Page newPage = pageService.addNewPage(PAGE_NAME, PAGE_LAYOUT_CODE);                
+        assertThat(newPage.getRenderSequence(), is(EXPECTED_RENDER_SEQUENCE));
+        assertThat(newPage.getName(), is(PAGE_NAME));
+        assertThat(newPage.getRegions().size(), is(pageLayout.getNumberOfRegions().intValue()));
+        
+        verify(userService);
+        verify(pageLayoutRepository);
+        verify(pageRepository);
+    }    
+   
+    @Test
+    public void createNewDefaultPage() {
+        final Long EXPECTED_RENDER_SEQUENCE = 1L;
+                      
+        Page expectedPage = new Page();
+        expectedPage.setName(defaultPageName);       
+        expectedPage.setOwner(user);
+        expectedPage.setPageLayout(pageLayout);
+        expectedPage.setRenderSequence(EXPECTED_RENDER_SEQUENCE);
+        expectedPage.setRegions(createEmptyRegionList(pageLayout.getNumberOfRegions()));    
+                
+        expect(pageLayoutRepository.getByPageLayoutCode(PAGE_LAYOUT_CODE)).andReturn(pageLayout);
+        expect(pageRepository.save(expectedPage)).andReturn(expectedPage);
+        expect(pageRepository.getAllPages(user.getId())).andReturn(new ArrayList<Page>());
+        replay(pageLayoutRepository);
+        replay(pageRepository);             
+
+        Page newPage = pageService.addNewDefaultPage(user, PAGE_LAYOUT_CODE);                
+        assertThat(newPage.getRenderSequence(), is(EXPECTED_RENDER_SEQUENCE));
+        assertThat(newPage.getName(), is(defaultPageName));
+        assertThat(newPage.getRegions().size(), is(pageLayout.getNumberOfRegions().intValue()));
+        
+        verify(pageLayoutRepository);
+        verify(pageRepository);
+    }
+    
+    @Test
+    public void getDefaultPageName() {
+        assertThat(pageService.getDefaultPageName(), is(defaultPageName));
+    }
+  
+    @Test
     public void moveRegionWidget_validMiddle() {
         final int newPosition = 0;
         createMoveBetweenRegionsExpectations();
@@ -297,4 +410,14 @@ public class PageServiceTest {
             assertThat(region.getRegionWidgets().get(i).getRenderOrder(), is(equalTo(i)));
         }
     }
+    
+    private List<Region> createEmptyRegionList(long numberOfRegions) {
+        List<Region> regions = new ArrayList<Region>();
+        int regionCount;
+        for (regionCount = 0; regionCount < numberOfRegions; regionCount++) {
+              Region region = new Region();
+              regions.add(region);
+        }
+        return regions;
+    }
 }
\ No newline at end of file

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/PageApiTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/PageApiTest.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/PageApiTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/PageApiTest.java Wed Aug 17 18:07:09 2011
@@ -19,6 +19,7 @@
 
 package org.apache.rave.portal.web.api.rpc;
 
+import org.apache.rave.portal.model.Page;
 import org.apache.rave.portal.model.Region;
 import org.apache.rave.portal.model.RegionWidget;
 import org.apache.rave.portal.service.PageService;
@@ -43,7 +44,7 @@ public class PageApiTest {
 
     @Before
     public void setup() {
-        pageService = createNiceMock(PageService.class);
+        pageService = createMock(PageService.class);
         pageApi = new PageApi(pageService);
     }
 
@@ -188,4 +189,52 @@ public class PageApiTest {
         assertThat(result.getErrorCode(), is(RpcResult.ErrorCode.INTERNAL_ERROR));
         assertThat(result.getErrorMessage(), is(equalTo(INTERNAL_ERROR_MESSAGE)));
     }
+    
+    @Test
+    public void addPage_validParams() {
+        final String PAGE_NAME = "My New Page";
+        final String PAGE_LAYOUT_CODE = "layout1";
+
+        expect(pageService.addNewPage(PAGE_NAME, PAGE_LAYOUT_CODE)).andReturn(new Page());
+        replay(pageService);
+        RpcResult result = pageApi.addPage(PAGE_NAME, PAGE_LAYOUT_CODE);
+        verify(pageService);
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getResult(), is(notNullValue()));
+        assertThat(result.isError(), is(false));
+        assertThat(result.getErrorCode(), is(RpcResult.ErrorCode.NO_ERROR));
+        assertThat(result.getErrorMessage(), is(nullValue()));
+    }
+    
+    @Test
+    public void addPage_invalidParams() {
+        final String PAGE_NAME = "My New Page";
+        final String PAGE_LAYOUT_CODE = "layout1";
+
+        expect(pageService.addNewPage(PAGE_NAME, PAGE_LAYOUT_CODE)).andThrow(new IllegalArgumentException(PARAM_ERROR_MESSAGE));
+        replay(pageService);
+        RpcResult result = pageApi.addPage(PAGE_NAME, PAGE_LAYOUT_CODE);
+        verify(pageService);
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getResult(), is(nullValue()));
+        assertThat(result.isError(), is(true));
+        assertThat(result.getErrorCode(), is(RpcResult.ErrorCode.INVALID_PARAMS));
+        assertThat(result.getErrorMessage(), is(equalTo(PARAM_ERROR_MESSAGE)));
+    }
+    
+    @Test
+    public void addPage_internalError() {
+        final String PAGE_NAME = "My New Page";
+        final String PAGE_LAYOUT_CODE = "layout1";
+
+        expect(pageService.addNewPage(PAGE_NAME, PAGE_LAYOUT_CODE)).andThrow(new RuntimeException(INTERNAL_ERROR_MESSAGE));
+        replay(pageService);
+        RpcResult result = pageApi.addPage(PAGE_NAME, PAGE_LAYOUT_CODE);
+        verify(pageService);
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getResult(), is(nullValue()));
+        assertThat(result.isError(), is(true));
+        assertThat(result.getErrorCode(), is(RpcResult.ErrorCode.INTERNAL_ERROR));
+        assertThat(result.getErrorMessage(), is(equalTo(INTERNAL_ERROR_MESSAGE)));
+    }
 }

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcOperationTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcOperationTest.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcOperationTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcOperationTest.java Wed Aug 17 18:07:09 2011
@@ -19,6 +19,7 @@
 
 package org.apache.rave.portal.web.api.rpc.model;
 
+import org.apache.rave.exception.DuplicateItemException;
 import org.junit.Test;
 
 import static org.hamcrest.CoreMatchers.*;
@@ -67,4 +68,17 @@ public class RpcOperationTest {
         assertThat(result.getResult(), is(nullValue()));
         assertThat(result.getErrorCode(), is(equalTo(RpcResult.ErrorCode.INTERNAL_ERROR)));
     }
+    @Test
+    public void execute_duplicateItemException() {
+        RpcOperation<String> valid = new RpcOperation<String>() {
+            @Override
+            public String execute() {
+                throw new DuplicateItemException("Duplicate Item Error");
+            }
+        };
+
+        RpcResult<String> result = valid.getResult();
+        assertThat(result.getResult(), is(nullValue()));
+        assertThat(result.getErrorCode(), is(equalTo(RpcResult.ErrorCode.DUPLICATE_ITEM)));
+    }    
 }

Modified: incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcResultTest.java
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcResultTest.java?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcResultTest.java (original)
+++ incubator/rave/trunk/rave-portal/src/test/java/org/apache/rave/portal/web/api/rpc/model/RpcResultTest.java Wed Aug 17 18:07:09 2011
@@ -28,11 +28,11 @@ import static org.junit.Assert.assertTha
 public class RpcResultTest {
 
     @Test
-    public void corretDefault_noError() {
+    public void correctDefault_noError() {
         assertThat(new RpcResult<String>(false).getErrorCode(), is(equalTo(RpcResult.ErrorCode.NO_ERROR)));
     }
     @Test
-    public void corretDefault_error() {
+    public void correctDefault_error() {
         assertThat(new RpcResult<String>(true).getErrorCode(), is(equalTo(RpcResult.ErrorCode.INTERNAL_ERROR)));
     }
 }

Modified: incubator/rave/trunk/rave-portal/src/test/javascript/raveApiSpec.js
URL: http://svn.apache.org/viewvc/incubator/rave/trunk/rave-portal/src/test/javascript/raveApiSpec.js?rev=1158852&r1=1158851&r2=1158852&view=diff
==============================================================================
--- incubator/rave/trunk/rave-portal/src/test/javascript/raveApiSpec.js (original)
+++ incubator/rave/trunk/rave-portal/src/test/javascript/raveApiSpec.js Wed Aug 17 18:07:09 2011
@@ -145,6 +145,26 @@ describe("Rave API", function() {
             });
 
         });
+        
+        describe("addPage", function() {
+            it("posts the correct values to RPC service for adding a new page", function() {
+
+                var newPageName = "my new page";
+                var newPageLayoutCode = "layout1";
+
+                $.post = function(url, data, callback) {
+                    expect(url).toEqual("api/rpc/page/add");
+                    expect(data.pageName).toEqual(newPageName);
+                    expect(data.pageLayoutCode).toEqual(newPageLayoutCode);
+                    expect(typeof(callback)).toEqual("function");
+                    return {
+                        error: function(a, b, c) {
+                        }
+                    }
+                };
+                rave.api.rpc.addPage({pageName: newPageName, pageLayoutCode: newPageLayoutCode});
+            });
+        });
 
         describe("Error handling", function() {
             it("displays the appropriate alert when invalid parameters are passed", function() {