Wiki

Clone wiki

grails-propertyset / Home

Grails PropertySet Plugin

Grails PropertySet Plugin is a persistence-agnostic module that can be used to fulfill storage requirements in applications that can change constantly. An example of this might be a "User Preferences" storage device. It may be impossible to know what the user can store at any given time in your application's lifecycle, so employing a PropertySet can help. Backed by GORM you can provide a complete typed key-value pair implementation.

Motivation

One of the most common questions when adding new functionality to your project is "where do I store my own configuration data?". The brute-force way is to add new tables or new columns to the database, but then your future upgrades will need the same changes. A better approach that's easier to maintain is to store your data in a property table.

In the "old days" to store configuration data, user preferences and system settings in a RDBMS I used a library from OpenSymphony called PropertySet which is still available today.

Unfortunately, OpenSymphony has seen it's final days. Started originally by some of the great minds of Open Source Java, it had a great run and produced some of the best Open Source Java libraries out there. Nevertheless PropertySet still has a future relevance and great companies like Atlassian are still developing and using OpenSymphony components.

Details

PropertySets provide an abstracted way to dynamically store and retrieve typed data from a persistence store. The Grails PropertySet Plugin provides a GORM PropertySet which is based on the PropertySet interface from OpenSymphony.

OpenSymphony's PropertySet is a framework that can store a set of properties (key/value pairs) against a particular "entity" with a unique id. An "entity" can be anything one wishes. For example, Atlassian JIRA's UserPropertyManager uses PropertySet to store user's preferences. Therefore, in this case, the "entity" is a User.

Each property has a key (which is always a java.lang.String) and a value, which can be:

  1. java.lang.String
  2. java.lang.Long
  3. java.util.Date
  4. java.lang.Double

Each property is always associated with one entity. As far as PropertySet is concerned an "entity" has an entity name, and a numeric id. As long as the same entity name/id combination is used to store the value and retrieve the value, everything will work.

The Grails PropertySet Plugin uses the following database tables:

  1. propertyentry - records the entity name and id for a property, its key, and the data type of the property's value. Each record in this table also has a unique id.
  2. propertystring - records String values
  3. propertytext - records Text values
  4. propertydecimal - records Double values
  5. propertydate - records Date values
  6. propertydata - records Blob values
  7. propertynumber - records Long values

Each of the records in property<type> tables also has an id column. The id is the same as the id of the propertyentry record for this property. As the property's key and value are split across 2 tables, to retrieve a property value, a join needs to be done, between propertyentry table and one of the property<type> tables. Which property<type> table to join with is determined by the value of the propertytype column in the propertyentry record.

Here is an example of a storing a address:

Example

Let's say I want to store two street addresses. In XML they might look something like this:

<address id="10010" housenumber="10" street="Oak Avenue" city="San Jose">
<address id="10200" housenumber="32" street="Long Street" city="London">

In the propertyentry table they would look like this

ID  ENTITY_NAME  ENTITY_ID  PROPERTY_KEY    PROPERTY_TYPE

100 Address      10010      House_Number    5
101 Address      10010      Street          5
102 Address      10010      City            5

103 Address      10200      House_Number    5
104 Address      10200      Street          5
105 Address      10200      City            5

First, the ID is a unique identifier for every piece of data in this database. Then the ENTITY_NAME "Address" is the kind of data we want to store. In an Object Relational Model (ORM) this is the class name of the object being stored.

ENTITY_ID is used distinguish multiple addresses. In ORM this is a unique identifier for each instance of an object. The property_key contains the name of a data field within each address. This is the "key" of the "key=value" pair that is being stored.

There's one last field in the propertyentry table - PROPERTY_TYPE. The most common value is 5, which means that the value is a string stored in the propertystring table. 1 is for boolean settings such as enabling or disabling voting and its value is stored in the propertynumber table. 6 is for blocks of text such as the license data and is in propertytext.

The values in propertystring for our example are:

ID   PROPERTY_VALUE

100  10
101  Oak Avenue
102  San Jose

103  32
104  Long Street
105  London

Accessing the Data

We first have to create a PropertySet object propertySet that knows how to refer to just one address, say the first one with has an entity id value of 10010.

This can be done with code such as

private static PropertySet getPS() {
        if (propertySet == null) {
            def args = [entityId:10010, entityName:"Address"]
            propertySet = PropertySetManager.getInstance("gorm", args );
        }
        return propertySet;
    }

Then we can use that to call

propertySet.setString("Street", "Pine Avenue");

to update the row in propertystring so that it looks like

ID   PROPERTY_VALUE
101  Pine Avenue

A similar method can be used to get the data:

String currentStreet = propertySet.getString("Street");

More Examples

User Properties are stored with a property key prefixed by "jira.meta.". So if you store a property "hair_color=brown", you will see an entry for the user with id 10000 in propertyentry such as

mysql> select * from propertyentry where property_key like 'jira.meta%';
+-------+-------------+-----------+-----------------------+---------------+
| ID    | ENTITY_NAME | ENTITY_ID | PROPERTY_KEY          | PROPERTY_TYPE |
+-------+-------------+-----------+-----------------------+---------------+
| 10111 | OSUser      |     10000 | jira.meta.hair_color  |            5  | 
+-------+-------------+-----------+-----------------------+---------------+

and an entry in propertystring such as

mysql> select * from propertystring where id=10111;
+-------+----------------+
| ID    | PROPERTY_VALUE |
+-------+----------------+
| 10111 | brown          | 
+-------+----------------+

Architecture

mysql> describe propertyentry;
+---------------+---------------+
| Field         | Type          |
+---------------+---------------+
| ID            | decimal(18,0) |
| ENTITY_NAME   | varchar(255)  |
| ENTITY_ID     | decimal(18,0) |
| PROPERTY_KEY  | varchar(255)  |
| PROPERTY_TYPE | decimal(9,0)  |
+---------------+---------------+

The documentation is a bit terse about what each of these fields is for: "each property has a record in the propertyentry table specifying its name and type, and a record in one of propertystring, propertydecimal, propertydate, propertytext, propertydata or propertynumber, depending on the type."

In more detail, the purposes of each of the fields in the propertyentry table are:

  • ID - a unique identifier for every piece of data
  • ENTITY_NAME - class name of the data object
  • ENTITY_ID - identifies an instance of the data object
  • PROPERTY_KEY - the key of the key=value pair
  • PROPERTY_TYPE - the data type of the value, e.g. 5 for a string

The values for PROPERTY_TYPE are correspsonding to the interface com.opensymphony.module.propertyset.PropertySet which declares the entries as follows:

int BOOLEAN = 1;
int INT = 2;
int LONG = 3;
int DOUBLE = 4;
int STRING = 5;
int TEXT = 6;
int DATE = 7;
int OBJECT = 8;
int XML = 9;
int DATA = 10;
int PROPERTIES = 11;

For each propertytype the corresponding table for the data value is:

propertyentry.propertytype valueTable value is stored inUsed for
1/2/3propertynumberboolean, short, int, long
4propertydecimalfloat, double
5propertystringMost fields, eg. full names, email addresses (string)
6propertytextLarge blocks of text, eg. the introduction text, HTML portletconfigurations
7propertydatedate
10propertydatablob

This results in the following ERD:

ERD

Example of a propertyentry table used by JIRA:

Example

Installation

Dependency:

compile "org.grails.plugins:propertyset:1.0.9"

Custom Repository: Add the following Maven Repository to your BuildConfig.groovy:

mavenRepo "http://repository.phasr.io/maven2/"

Grails Quick-Start

Old style (<= v1.0.8)

def args = [entityId:1, entityName:"com.opensymphony.module.propertyset.settings"]
PropertySet ps = PropertySetManager.getInstance("gorm", args, new GrailsAwareClassLoader(getClass().getClassLoader()))

ps.setText( "sampleTextProperty" , "Demo")
ps.setString( "sampleStringProperty" , "http://www.google.de" )

New Style (>= v1.0.9)

def propertySetService
PropertySet ps = propertySetService.getProperySet(1, "com.opensymphony.module.propertyset.settings")

ps.setText( "sampleTextProperty" , "Demo")
ps.setString( "sampleStringProperty" , "http://www.google.de" )

History

Version 1.0.9

  • PropertySet available as simple Service

Version 1.0.1

  • upgraded to Grails 2.1.1
  • made available through Maven2 repository

Version 1.0.0

  • initial release
  • based on OpenSymphony PropertySet API 1.6.0, OpenSymphony PropertySet Core 1.6.0 and OpenSymphony OSCore 2.2.7
  1. OpenSymphony
  2. OpenSymphony Propertyset on the Wayback Machine
  3. OpenSymphony on Bitbucket
  4. OpenSymphony Propertyset on Bitbucket
  5. PropertySet
  6. JIRA Architectural Overview

Description

  • "persistence storage of data-typed properties"
  • "A PropertySet is designed to be associated with other entities in the system for storing key/value property pairs. A key can only contain one value and a key is unique across all types. If a property is set using the same key and an already existing property of the SAME type, the new value will overwrite the old."

Updated