RAVE-1275 | Ability to set properties on regions in a page template and have them show up in the derived page
diff --git a/rave-components/rave-core-api/src/main/java/org/apache/rave/model/PageTemplateRegion.java b/rave-components/rave-core-api/src/main/java/org/apache/rave/model/PageTemplateRegion.java
index 0a4f5ad..9ea89a8 100644
--- a/rave-components/rave-core-api/src/main/java/org/apache/rave/model/PageTemplateRegion.java
+++ b/rave-components/rave-core-api/src/main/java/org/apache/rave/model/PageTemplateRegion.java
@@ -20,6 +20,7 @@
 
 import javax.xml.bind.annotation.XmlTransient;
 import java.util.List;
+import java.util.Map;
 
 /**
  */
@@ -44,4 +45,19 @@
 
     void setLocked(boolean locked);
 
+    /**
+     * Generic property bag for extension of the region object.
+     *
+     * Rave makes no attempt to understand the shape of properties in the bag.
+     *
+     * @return a valid Map of String to JSON Serializable Object.
+     */
+    Map<String, Object> getProperties();
+
+    /**
+     * Overrides the current properties with a new set.
+     *
+     * @param properties a non-null map of string to JSON serializable objects
+     */
+    void setProperties(Map<String, Object> properties);
 }
diff --git a/rave-components/rave-core-api/src/main/java/org/apache/rave/model/Region.java b/rave-components/rave-core-api/src/main/java/org/apache/rave/model/Region.java
index 93b0a1f..37fe534 100644
--- a/rave-components/rave-core-api/src/main/java/org/apache/rave/model/Region.java
+++ b/rave-components/rave-core-api/src/main/java/org/apache/rave/model/Region.java
@@ -20,6 +20,7 @@
 
 import javax.xml.bind.annotation.XmlTransient;
 import java.util.List;
+import java.util.Map;
 
 @XmlTransient
 public interface Region {
@@ -40,4 +41,20 @@
     boolean isLocked();
 
     void setLocked(boolean locked);
+
+    /**
+     * Generic property bag for extension of the region object.
+     *
+     * Rave makes no attempt to understand the shape of properties in the bag.
+     *
+     * @return a valid Map of String to JSON Serializable Object.
+     */
+    Map<String, Object> getProperties();
+
+    /**
+     * Overrides the current properties with a new set.
+     *
+     * @param properties a non-null map of string to JSON serializable objects
+     */
+    void setProperties(Map<String, Object> properties);
 }
diff --git a/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/model/Region.java b/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/model/Region.java
index 0d347bc..377b4a2 100644
--- a/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/model/Region.java
+++ b/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/model/Region.java
@@ -22,6 +22,8 @@
 import javax.xml.bind.annotation.*;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+
 @XmlAccessorType(XmlAccessType.FIELD)
 @XmlType(name = "Region", propOrder = {
         "locked", "regionWidgets"
@@ -35,6 +37,8 @@
     @XmlElementWrapper(name = "regionWidgets")
     @XmlElement(name="RegionWidget")
     private List<RegionWidget> regionWidgets;
+    @XmlElement(name = "properties")
+    private Map<String, Object> properties;
 
     public Region() { }
 
@@ -68,6 +72,14 @@
         this.regionWidgets = regionWidgets;
     }
 
+    public Map<String, Object> getProperties() {
+        return this.properties;
+    }
+
+    public void setProperties(Map<String, Object> properties) {
+        this.properties = properties;
+    }
+
     private List<RegionWidget> createRegionWidgets(org.apache.rave.model.Region source) {
         List<RegionWidget> created = null;
         List<org.apache.rave.model.RegionWidget> widgets = source.getRegionWidgets();
diff --git a/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/PageTemplateRegionImpl.java b/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/PageTemplateRegionImpl.java
index 312bd98..df733e8 100644
--- a/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/PageTemplateRegionImpl.java
+++ b/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/PageTemplateRegionImpl.java
@@ -23,6 +23,7 @@
 import org.apache.rave.model.PageTemplateWidget;
 
 import java.util.List;
+import java.util.Map;
 
 public class PageTemplateRegionImpl implements PageTemplateRegion {
     private String id;
@@ -30,6 +31,7 @@
     private PageTemplate pageTemplate;
     private List<PageTemplateWidget> pageTemplateWidgets;
     private boolean locked;
+    private Map<String, Object> properties;
 
     public PageTemplateRegionImpl() {
 
@@ -80,6 +82,16 @@
     }
 
     @Override
+    public Map<String, Object> getProperties() {
+        return this.properties;
+    }
+
+    @Override
+    public void setProperties(Map<String, Object> properties) {
+        this.properties = properties;
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (!(o instanceof PageTemplateRegionImpl)) return false;
diff --git a/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/RegionImpl.java b/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/RegionImpl.java
index 31f0c13..962fb80 100644
--- a/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/RegionImpl.java
+++ b/rave-components/rave-core/src/main/java/org/apache/rave/portal/model/impl/RegionImpl.java
@@ -24,6 +24,7 @@
 import com.fasterxml.jackson.annotation.JsonBackReference;
 
 import java.util.List;
+import java.util.Map;
 
 public class RegionImpl implements Region {
     private String id;
@@ -31,6 +32,7 @@
     private Boolean locked = false;
     private Integer renderOrder = 0;
     private List<RegionWidget> regionWidgets;
+    private Map<String, Object> properties;
 
     public RegionImpl() {
 
@@ -97,6 +99,16 @@
     }
 
     @Override
+    public Map<String, Object> getProperties() {
+        return this.properties;
+    }
+
+    @Override
+    public void setProperties(Map<String, Object> properties) {
+        this.properties = properties;
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (!(o instanceof RegionImpl)) return false;
diff --git a/rave-components/rave-core/src/main/java/org/apache/rave/portal/util/PageUtil.java b/rave-components/rave-core/src/main/java/org/apache/rave/portal/util/PageUtil.java
index ee8037d..93244ba 100644
--- a/rave-components/rave-core/src/main/java/org/apache/rave/portal/util/PageUtil.java
+++ b/rave-components/rave-core/src/main/java/org/apache/rave/portal/util/PageUtil.java
@@ -82,6 +82,7 @@
             region.setPage(page);
             region.setLocked(ptr.isLocked());
             region.setRegionWidgets(convertWidgets(ptr.getPageTemplateWidgets(), region, createIds));
+            region.setProperties(ptr.getProperties());
             regions.add(region);
         }
         return regions;
diff --git a/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaPageTemplateRegion.java b/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaPageTemplateRegion.java
index c6048b4..349320b 100644
--- a/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaPageTemplateRegion.java
+++ b/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaPageTemplateRegion.java
@@ -19,6 +19,7 @@
 

 package org.apache.rave.portal.model;

 

+import com.google.common.collect.Maps;

 import org.apache.rave.model.PageTemplate;

 import org.apache.rave.model.PageTemplateRegion;

 import org.apache.rave.model.PageTemplateWidget;

@@ -29,6 +30,7 @@
 import java.io.Serializable;

 import java.util.ArrayList;

 import java.util.List;

+import java.util.Map;

 

 @Entity

 @Table(name = "page_template_region")

@@ -127,4 +129,14 @@
     public void setLocked(boolean locked) {

         this.locked = locked;

     }

+

+    @Override

+    public Map<String, Object> getProperties() {

+        return Maps.newHashMap();

+    }

+

+    @Override

+    public void setProperties(Map<String, Object> properties) {

+        // TODO: Implement this

+    }

 }

diff --git a/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaRegion.java b/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaRegion.java
index 9ad1887..823b68b 100644
--- a/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaRegion.java
+++ b/rave-components/rave-jpa/src/main/java/org/apache/rave/portal/model/JpaRegion.java
@@ -19,6 +19,7 @@
 package org.apache.rave.portal.model;
 
 import com.fasterxml.jackson.annotation.JsonManagedReference;
+import com.google.common.collect.Maps;
 import org.apache.rave.model.Page;
 import org.apache.rave.model.Region;
 import org.apache.rave.model.RegionWidget;
@@ -33,6 +34,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A region of a page, which can contain widget instances {@link RegionWidget}
@@ -193,6 +195,16 @@
     }
 
     @Override
+    public Map<String, Object> getProperties() {
+        return Maps.newHashMap();
+    }
+
+    @Override
+    public void setProperties(Map<String, Object> properties) {
+        // TODO: Implement this
+    }
+
+    @Override
     public boolean equals(Object obj) {
         if (obj == null) {
             return false;
diff --git a/rave-components/rave-mongodb/src/main/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageConverter.java b/rave-components/rave-mongodb/src/main/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageConverter.java
index 04ffaef..63b94fa 100644
--- a/rave-components/rave-mongodb/src/main/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageConverter.java
+++ b/rave-components/rave-mongodb/src/main/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageConverter.java
@@ -169,6 +169,7 @@
         String regionId = region.getId() == null ? generateId() : region.getId();
         Region converted = new RegionImpl(regionId, null, region.getRenderOrder());
         converted.setLocked(region.isLocked());
+        converted.setProperties(region.getProperties());
         if (region.getRegionWidgets() != null) {
             List<RegionWidget> convertedWidgets = Lists.newArrayList();
             for (RegionWidget widget : region.getRegionWidgets()) {
diff --git a/rave-components/rave-mongodb/src/test/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageTemplateConverterTest.java b/rave-components/rave-mongodb/src/test/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageTemplateConverterTest.java
index ae19bb4..cd5c0d1 100644
--- a/rave-components/rave-mongodb/src/test/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageTemplateConverterTest.java
+++ b/rave-components/rave-mongodb/src/test/java/org/apache/rave/portal/model/conversion/impl/MongoDbPageTemplateConverterTest.java
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 import static org.easymock.EasyMock.*;
 import static org.hamcrest.CoreMatchers.*;
@@ -200,7 +201,17 @@
                 public void setLocked(boolean locked) {
                     //To change body of implemented methods use File | Settings | File Templates.
                 }
-            };
+
+                @Override
+                public Map<String, Object> getProperties() {
+                    return null;
+                }
+
+                @Override
+                public void setProperties(Map<String, Object> properties) {
+
+                }
+        };
     }
 
     @Test
diff --git a/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/main/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetWrapperRenderer.java b/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/main/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetWrapperRenderer.java
index 04aa618..c128516 100644
--- a/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/main/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetWrapperRenderer.java
+++ b/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/main/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetWrapperRenderer.java
@@ -76,7 +76,8 @@
             " locked: %10$s," +
             " hideChrome: %11$s," +
             " subPage: {id: %12$s, name: '%13$s', isDefault: %14$s}," +
-            " properties: %15$s" +
+            " properties: %15$s," +
+            " regionProperties: %16$s," +
             "})});</script>";
     private static final String MARKUP = "<!-- RegionWidget '%1$s' placeholder -->";
 
@@ -157,7 +158,8 @@
                 pageId,
                 pageName,
                 isDefault,
-                JsonUtils.stringify(widget.getProperties())
+                JsonUtils.stringify(widget.getProperties()),
+                JsonUtils.stringify(item.getRegion().getProperties()),
                 );
     }
 
diff --git a/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/test/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetRendererTest.java b/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/test/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetRendererTest.java
index b81e108..d113278 100644
--- a/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/test/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetRendererTest.java
+++ b/rave-providers/rave-opensocial-provider/rave-opensocial-client/src/test/java/org/apache/rave/provider/opensocial/web/renderer/OpenSocialWidgetRendererTest.java
@@ -103,6 +103,7 @@
         props.put("foo","bar");
 
         Region region = new RegionImpl(REGION_ID);
+        region.setProperties(ImmutableMap.<String, Object>of("regionFoo", "regionBar"));
         region.setPage(subPage);
         RegionWidget rw = new RegionWidgetImpl(REGION_WIDGET_ID);
         rw.setCollapsed(VALID_COLLAPSED);
@@ -127,7 +128,8 @@
                         " locked: " + VALID_LOCKED + "," +
                         " hideChrome: " + VALID_HIDE_CHROME + "," +
                         " subPage: {id: '" + VALID_SUBPAGE_ID + "', name: '" + VALID_SUBPAGE_NAME + "', isDefault: " + VALID_IS_DEFAULT_SUBPAGE + "}," +
-                        " properties: {\"foo\":\"bar\"}" +
+                        " properties: {\"foo\":\"bar\"}," +
+                        " regionProperties: {\"regionFoo\":\"regionBar\"}," +
                         "})" +
                         "});</script>";
 
@@ -178,7 +180,8 @@
                         " locked: false," +
                         " hideChrome: false," +
                         " subPage: {id: null, name: '', isDefault: false}," +
-                        " properties: null" +
+                        " properties: null," +
+                        " regionProperties: null," +
                         "})" +
                         "});</script>";