You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by se...@apache.org on 2015/06/24 14:24:52 UTC

[3/3] incubator-ignite git commit: # IGNITE-843 Implemented cache generator for XML

# IGNITE-843 Implemented cache generator for XML


Project: http://git-wip-us.apache.org/repos/asf/incubator-ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ignite/commit/c2be3e43
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ignite/tree/c2be3e43
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ignite/diff/c2be3e43

Branch: refs/heads/ignite-843
Commit: c2be3e4370dae3747d3e1cb78083686e357ea2b7
Parents: 93927c4
Author: sevdokimov <se...@gridgain.com>
Authored: Wed Jun 24 15:24:33 2015 +0300
Committer: sevdokimov <se...@gridgain.com>
Committed: Wed Jun 24 15:24:33 2015 +0300

----------------------------------------------------------------------
 modules/webconfig/nodejs/utils/generatorJava.js |  97 ++++------
 .../webconfig/nodejs/utils/generatorUtils.js    |  71 ++++++-
 modules/webconfig/nodejs/utils/generatorXml.js  | 185 ++++++++++++++++---
 3 files changed, 266 insertions(+), 87 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/c2be3e43/modules/webconfig/nodejs/utils/generatorJava.js
----------------------------------------------------------------------
diff --git a/modules/webconfig/nodejs/utils/generatorJava.js b/modules/webconfig/nodejs/utils/generatorJava.js
index ced6ac3..d36a3ae 100644
--- a/modules/webconfig/nodejs/utils/generatorJava.js
+++ b/modules/webconfig/nodejs/utils/generatorJava.js
@@ -107,10 +107,8 @@ exports.generateClusterConfiguration = function(cluster) {
         res.needEmptyLine = true;
     }
 
-    addBeanWithProperties(res, cluster.atomicConfiguration, 'cfg', 'atomicConfiguration', 'atomicCfg', 'AtomicConfiguration', {
-        backups: null,
-        cacheMode: 'CacheMode',
-        atomicSequenceReserveSize: null});
+    addBeanWithProperties(res, cluster.atomicConfiguration, 'cfg', 'atomicConfiguration', 'atomicCfg',
+        generatorUtils.atomicConfiguration.shortClassName, generatorUtils.atomicConfiguration.fields);
 
     res.needEmptyLine = true;
 
@@ -127,7 +125,7 @@ exports.generateClusterConfiguration = function(cluster) {
 
     res.needEmptyLine = true;
 
-    addListProperty(res, cluster, 'cfg', 'includeEventTypes');
+    addMultiparamProperty(res, cluster, 'cfg', 'includeEventTypes', 'EventType');
 
     res.needEmptyLine = true;
 
@@ -149,13 +147,8 @@ exports.generateClusterConfiguration = function(cluster) {
     addProperty(res, cluster, 'cfg', 'peerClassLoadingThreadPoolSize');
     res.needEmptyLine = true;
 
-    addBeanWithProperties(res, cluster.swapSpaceSpi.FileSwapSpaceSpi, 'cfg', 'swapSpaceSpi', 'swapSpi', 'FileSwapSpaceSpi', {
-        baseDirectory: null,
-        readStripesNumber: null,
-        maximumSparsity: 'f',
-        maxWriteQueueSize: null,
-        writeBufferSize: null
-    }, true);
+    addBeanWithProperties(res, cluster.swapSpaceSpi.FileSwapSpaceSpi, 'cfg', 'swapSpaceSpi', 'swapSpi',
+        generatorUtils.swapSpaceSpi.shortClassName, generatorUtils.swapSpaceSpi.fields, true);
 
     res.needEmptyLine = true;
 
@@ -174,14 +167,8 @@ exports.generateClusterConfiguration = function(cluster) {
     res.needEmptyLine = true;
 
     addBeanWithProperties(res, cluster.transactionConfiguration, 'cfg', 'transactionConfiguration',
-        'transactionConfiguration', 'TransactionConfiguration', {
-            defaultTxConcurrency: 'TransactionConcurrency',
-            transactionIsolation: 'TransactionIsolation',
-            defaultTxTimeout: null,
-            pessimisticTxLogLinger: null,
-            pessimisticTxLogSize: null,
-            txSerializableEnabled: null
-        });
+        'transactionConfiguration', generatorUtils.transactionConfiguration.shortClassName,
+        generatorUtils.transactionConfiguration.fields);
 
     res.needEmptyLine = true;
 
@@ -220,11 +207,7 @@ exports.generateCacheConfiguration = function(cacheCfg, varName, res) {
     
     res.needEmptyLine = true;
 
-    if (cacheCfg.mode) {
-        res.emptyLineIfNeeded();
-
-        res.line(varName + '.setCacheMode(CacheMode.' + cacheCfg.mode  + ');');
-    }
+    addProperty(res, cacheCfg, varName, 'mode', 'CacheMode', 'cacheMode');
 
     addProperty(res, cacheCfg, varName, 'atomicityMode', 'CacheAtomicityMode');
     addProperty(res, cacheCfg, varName, 'backups');
@@ -365,13 +348,14 @@ function toJavaCode(val, type) {
     throw "Unknown type: " + typeof(val) + ' (' + val + ')';
 }
 
-function addProperty(res, obj, objVariableName, propName, enumType) {
+function addProperty(res, obj, objVariableName, propName, enumType, setterName) {
     var val = obj[propName];
     
     if (val) {
         res.emptyLineIfNeeded();
-        
-        res.line(objVariableName + '.' + getSetterName(propName) + '(' + toJavaCode(val, enumType)  + ');');
+
+        res.line(objVariableName + '.' + getSetterName(setterName ? setterName : propName)
+            + '(' + toJavaCode(val, enumType)  + ');');
     }
 }
 
@@ -379,13 +363,11 @@ function getSetterName(propName) {
     return 'set' + propName.charAt(0).toLocaleUpperCase() + propName.slice(1);
 }
 
-function addListProperty(res, obj, objVariableName, propName, enumType) {
+function addListProperty(res, obj, objVariableName, propName, enumType, setterName) {
     var val = obj[propName];
     
     if (val && val.length > 0) {
-        var setterName = getSetterName(propName);
-        
-        res.append(objVariableName + '.' + setterName + '(Arrays.asList(');
+        res.append(objVariableName + '.' + getSetterName(setterName ? setterName : propName) + '(Arrays.asList(');
 
         for (var i = 0; i < val.length; i++) {
             if (i > 0)
@@ -398,13 +380,11 @@ function addListProperty(res, obj, objVariableName, propName, enumType) {
     }
 }
 
-function addMultiparamProperty(res, obj, objVariableName, propName, type) {
+function addMultiparamProperty(res, obj, objVariableName, propName, type, setterName) {
     var val = obj[propName];
     
     if (val && val.length > 0) {
-        var setterName = getSetterName(propName);
-        
-        res.append(objVariableName + '.' + setterName + '(');
+        res.append(objVariableName + '.' + getSetterName(setterName ? setterName : propName) + '(');
 
         for (var i = 0; i < val.length; i++) {
             if (i > 0)
@@ -417,46 +397,47 @@ function addMultiparamProperty(res, obj, objVariableName, propName, type) {
     }
 }
 
-function addBeanWithProperties(res, bean, objVarName, beanPropName, beanVarName, beanClass, props, alwaysCreateBean) {
+function addBeanWithProperties(res, bean, objVarName, beanPropName, beanVarName, beanClass, props, createBeanAlthoughNoProps) {
     if (!bean)
         return;
     
-    var hasProps = false;
-    for (var propName in props) {
-        if (props.hasOwnProperty(propName)) {
-            if (bean[propName]) {
-                hasProps = true;
-                break;
-            }
-        }
-    }
-    
-    if (hasProps) {
+    if (generatorUtils.hasProperty(bean, props)) {
         if (!res.emptyLineIfNeeded()) {
             res.line();
         }
         
         res.line(beanClass + ' ' + beanVarName + ' = new ' + beanClass + '();');
-        for (propName in props) {
+        for (var propName in props) {
             if (props.hasOwnProperty(propName)) {
-                var val = bean[propName];
-                if (val) {
-                    var type = props[propName];
-                    
-                    if (type == 'list') {
-                        addListProperty(res, bean, beanVarName, propName);
+                var setterName = null;
+                var type = null;
+
+                var descr = props[propName];
+
+                if (descr) {
+                    if (typeof(descr) == 'string') {
+                        type = descr;
                     }
-                    else {
-                        addProperty(res, bean, beanVarName, propName, type);
+                    if (typeof(descr) == 'object') {
+                        type = descr.type;
+
+                        setterName = descr.setterName
                     }
                 }
+
+                if (type == 'list') {
+                    addListProperty(res, bean, beanVarName, propName, type, setterName);
+                }
+                else {
+                    addProperty(res, bean, beanVarName, propName, type, setterName);
+                }
             }
         }
         res.line(objVarName + '.' + getSetterName(beanPropName) + '(' + beanVarName + ');');
         
         res.needEmptyLine = true;
     }
-    else if (alwaysCreateBean) {
+    else if (createBeanAlthoughNoProps) {
         res.emptyLineIfNeeded();
         
         res.line(objVarName + '.' + getSetterName(beanPropName) + '(new ' + beanClass + '());');

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/c2be3e43/modules/webconfig/nodejs/utils/generatorUtils.js
----------------------------------------------------------------------
diff --git a/modules/webconfig/nodejs/utils/generatorUtils.js b/modules/webconfig/nodejs/utils/generatorUtils.js
index d29e35c..1d0b9de 100644
--- a/modules/webconfig/nodejs/utils/generatorUtils.js
+++ b/modules/webconfig/nodejs/utils/generatorUtils.js
@@ -104,14 +104,73 @@ exports.builder = function () {
     return res;
 };
 
+function ClassDescriptor(className, fields) {
+    this.className = className;
+
+    this.shortClassName = className.substr(className.lastIndexOf('.') + 1);
+
+    this.fields = fields;
+}
+
 exports.evictionPolicies = {
-    'LRU': {shortClassName: 'LruEvictionPolicy', fields: {batchSize: null, maxMemorySize: null, maxSize: null}},
-    'RND': {shortClassName: 'RandomEvictionPolicy', fields: {maxSize: null}},
-    'FIFO': {shortClassName: 'FifoEvictionPolicy', fields: {batchSize: null, maxMemorySize: null, maxSize: null}},
-    'SORTED': {shortClassName: 'SortedEvictionPolicy', fields: {batchSize: null, maxMemorySize: null, maxSize: null}}
+    'LRU': new ClassDescriptor('org.apache.ignite.cache.eviction.lru.LruEvictionPolicy',
+        {batchSize: null, maxMemorySize: null, maxSize: null}),
+    'RND': new ClassDescriptor('org.apache.ignite.cache.eviction.random.RandomEvictionPolicy', {maxSize: null}),
+    'FIFO': new ClassDescriptor('org.apache.ignite.cache.eviction.fifo.FifoEvictionPolicy',
+        {batchSize: null, maxMemorySize: null, maxSize: null}),
+    'SORTED': new ClassDescriptor('org.apache.ignite.cache.eviction.sorted.SortedEvictionPolicy',
+        {batchSize: null, maxMemorySize: null, maxSize: null})
 };
 
+exports.storeFactories = {
+    CacheJdbcPojoStoreFactory: new ClassDescriptor('org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory', {
+        dataSourceBean: null,
+        dialect: null
+    }),
+
+    CacheJdbcBlobStoreFactory: new ClassDescriptor('org.apache.ignite.cache.store.jdbc.CacheJdbcBlobStoreFactory', {
+        multicastGroup: null,
+        multicastPort: null,
+        responseWaitTime: null,
+        addressRequestAttempts: null,
+        localAddress: null
+    }),
+
+    CacheHibernateBlobStoreFactory: new ClassDescriptor('org.apache.ignite.cache.store.hibernate.CacheHibernateBlobStoreFactory', {
+        hibernateProperties: 'list'
+    })
+};
 
-exports.writeProperties = function(writer, cluster) {
-    
+exports.atomicConfiguration = new ClassDescriptor('org.apache.ignite.configuration.AtomicConfiguration', {
+    backups: null,
+    cacheMode: 'CacheMode',
+    atomicSequenceReserveSize: null
+});
+
+exports.swapSpaceSpi = new ClassDescriptor('org.apache.ignite.spi.swapspace.file.FileSwapSpaceSpi', {
+    baseDirectory: null,
+    readStripesNumber: null,
+    maximumSparsity: 'f',
+    maxWriteQueueSize: null,
+    writeBufferSize: null
+});
+
+exports.transactionConfiguration = new ClassDescriptor('org.apache.ignite.configuration.TransactionConfiguration', {
+    defaultTxConcurrency: 'TransactionConcurrency',
+    transactionIsolation: {type: 'TransactionIsolation', setterName: 'defaultTxIsolation'},
+    defaultTxTimeout: null,
+    pessimisticTxLogLinger: null,
+    pessimisticTxLogSize: null,
+    txSerializableEnabled: null
+});
+
+exports.hasProperty = function(obj, props) {
+    for (var propName in props) {
+        if (props.hasOwnProperty(propName)) {
+            if (obj[propName])
+                return true;
+        }
+    }
+
+    return false;
 };
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/c2be3e43/modules/webconfig/nodejs/utils/generatorXml.js
----------------------------------------------------------------------
diff --git a/modules/webconfig/nodejs/utils/generatorXml.js b/modules/webconfig/nodejs/utils/generatorXml.js
index 7dc9cbf..4e002d0 100644
--- a/modules/webconfig/nodejs/utils/generatorXml.js
+++ b/modules/webconfig/nodejs/utils/generatorXml.js
@@ -25,9 +25,11 @@ exports.generateClusterConfiguration = function(cluster) {
         '\n' +
         '<!-- ' + (generatorUtils.mainComment.replace('$date', generatorUtils.formatDate(new Date()))) + ' -->\n' +    
         '<beans xmlns="http://www.springframework.org/schema/beans"\n' +
-        '       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' +
+        '       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"\n' +
         '       xsi:schemaLocation="http://www.springframework.org/schema/beans\n' +
-        '                           http://www.springframework.org/schema/beans/spring-beans.xsd">\n' +
+        '                           http://www.springframework.org/schema/beans/spring-beans.xsd\n' +
+        '                           http://www.springframework.org/schema/util\n' +
+        '                           http://www.springframework.org/schema/util/spring-util.xsd">\n' +
         '    <bean class="org.apache.ignite.configuration.IgniteConfiguration">\n');
 
     res.deep = 2;
@@ -133,8 +135,9 @@ exports.generateClusterConfiguration = function(cluster) {
         res.needEmptyLine = true
     }
 
-    addBeanWithProperties(res, cluster.atomicConfiguration, 'atomicConfiguration', 
-        'org.apache.ignite.configuration.AtomicConfiguration', ['backups', 'cacheMode', 'atomicSequenceReserveSize']);
+    addBeanWithProperties(res, cluster.atomicConfiguration, 'atomicConfiguration',
+        generatorUtils.atomicConfiguration.className, generatorUtils.atomicConfiguration.fields);
+
     res.needEmptyLine = true;
 
     addProperty(res, cluster, 'networkTimeout');
@@ -150,7 +153,9 @@ exports.generateClusterConfiguration = function(cluster) {
 
     res.needEmptyLine = true;
 
-    addListProperty(res, cluster, 'includeEventTypes');
+    addListProperty(res, cluster, 'includeEventTypes', 'list', function(val) {
+        return '<util:constant static-field="org.apache.ignite.events.EventType.' + val + '"/>'
+    });
 
     res.needEmptyLine = true;
 
@@ -175,8 +180,7 @@ exports.generateClusterConfiguration = function(cluster) {
     res.needEmptyLine = true;
 
     addBeanWithProperties(res, cluster.swapSpaceSpi.FileSwapSpaceSpi, 'swapSpaceSpi',
-        'org.apache.ignite.spi.swapspace.file.FileSwapSpaceSpi', ['baseDirectory', 'readStripesNumber',
-            'maximumSparsity', 'maxWriteQueueSize', 'writeBufferSize'], true);
+        generatorUtils.swapSpaceSpi.className, generatorUtils.swapSpaceSpi.fields, true);
 
     res.needEmptyLine = true;
     
@@ -195,10 +199,7 @@ exports.generateClusterConfiguration = function(cluster) {
     res.needEmptyLine = true;
     
     addBeanWithProperties(res, cluster.transactionConfiguration, 'transactionConfiguration',
-        'org.apache.ignite.configuration.TransactionConfiguration', ['defaultTxConcurrency', 'transactionIsolation',
-            'defaultTxTimeout', 'pessimisticTxLogLinger',
-            'pessimisticTxLogSize', 'txSerializableEnabled']);
-
+        generatorUtils.transactionConfiguration.className, generatorUtils.transactionConfiguration.fields);
 
     res.needEmptyLine = true;
 
@@ -222,26 +223,134 @@ exports.generateClusterConfiguration = function(cluster) {
     return res.join('');
 };
 
+function createEvictionPolicy(res, evictionPolicy, propertyName) {
+    if (evictionPolicy && evictionPolicy.kind) {
+        var e = generatorUtils.evictionPolicies[evictionPolicy.kind];
+
+        var obj = evictionPolicy[evictionPolicy.kind.toUpperCase()];
+
+        addBeanWithProperties(res, obj, propertyName, e.className, e.fields, true);
+    }
+}
+
 exports.generateCacheConfiguration = function(cacheCfg, varName, res) {
     if (!res)
         res = generatorUtils.builder();
 
-    res.line('cache');
+    res.startBlock('<bean class="org.apache.ignite.configuration.CacheConfiguration">');
+
+    addProperty(res, cacheCfg, 'mode', 'cacheMode');
+
+    addProperty(res, cacheCfg, 'atomicityMode');
+    addProperty(res, cacheCfg, 'backups');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'memoryMode');
+    addProperty(res, cacheCfg, 'offHeapMaxMemory');
+    addProperty(res, cacheCfg, 'swapEnabled');
+
+    res.needEmptyLine = true;
+
+    createEvictionPolicy(res, cacheCfg.evictionPolicy, 'evictionPolicy');
+
+    res.needEmptyLine = true;
+
+    if (cacheCfg.nearConfiguration) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="nearConfiguration">');
+        res.startBlock('<bean class="org.apache.ignite.configuration.NearCacheConfiguration">');
+
+        addProperty(res, cacheCfg.nearConfiguration, 'nearStartSize');
+        addProperty(res, cacheCfg.nearConfiguration, 'atomicSequenceReserveSize');
+
+        createEvictionPolicy(res, cacheCfg.nearConfiguration.nearEvictionPolicy, 'nearEvictionPolicy');
+
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+    }
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, varName, 'sqlEscapeAll');
+    addProperty(res, cacheCfg, varName, 'sqlOnheapRowCacheSize');
+    addProperty(res, cacheCfg, varName, 'longQueryWarningTimeout');
+
+    if (cacheCfg.indexedTypes && cacheCfg.indexedTypes.length > 0) {
+        res.startBlock('<property name="indexedTypes">');
+        res.startBlock('<array>');
+
+        for (var i = 0; i < cacheCfg.indexedTypes.length; i++) {
+            var pair = cacheCfg.indexedTypes[i];
+
+            res.line('<value>' + escape(pair.keyClass) + '</value>');
+            res.line('<value>' + escape(pair.valueClass) + '</value>');
+        }
+
+        res.endBlock('</array>');
+        res.endBlock('</property>');
+    }
+
+    addListProperty(res, cacheCfg, 'sqlFunctionClasses', 'array');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'rebalanceMode');
+    addProperty(res, cacheCfg, 'rebalanceThreadPoolSize');
+    addProperty(res, cacheCfg, 'rebalanceBatchSize');
+    addProperty(res, cacheCfg, 'rebalanceOrder');
+    addProperty(res, cacheCfg, 'rebalanceDelay');
+    addProperty(res, cacheCfg, 'rebalanceTimeout');
+    addProperty(res, cacheCfg, 'rebalanceThrottle');
+
+    res.needEmptyLine = true;
+
+    if (cacheCfg.store && cacheCfg.store.kind) {
+        var obj = cacheCfg.store[cacheCfg.store.kind];
+        var data = generatorUtils.storeFactories[cacheCfg.store.kind];
+
+        addBeanWithProperties(res, obj, 'cacheStoreFactory', data.className, data.fields, true);
+    }
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'invalidate');
+    addProperty(res, cacheCfg, 'defaultLockTimeout');
+    addProperty(res, cacheCfg, 'transactionManagerLookupClassName');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'writeBehindEnabled');
+    addProperty(res, cacheCfg, 'writeBehindBatchSize');
+    addProperty(res, cacheCfg, 'writeBehindFlushSize');
+    addProperty(res, cacheCfg, 'writeBehindFlushFrequency');
+    addProperty(res, cacheCfg, 'writeBehindFlushThreadCount');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'statisticsEnabled');
+    addProperty(res, cacheCfg, 'managementEnabled');
+    addProperty(res, cacheCfg, 'readFromBackup');
+    addProperty(res, cacheCfg, 'copyOnRead');
+    addProperty(res, cacheCfg, 'maxConcurrentAsyncOperations');
     
+    res.endBlock('</bean>');
+
     return res;
 };
 
-function addProperty(res, obj, propName) {
+function addProperty(res, obj, propName, setterName) {
     var val = obj[propName];
 
     if (val) {
         res.emptyLineIfNeeded();
 
-        res.line('<property name="' + propName + '" value="' + escapeAttr(val) + '"/>');
+        res.line('<property name="' + (setterName ? setterName : propName) + '" value="' + escapeAttr(val) + '"/>');
     }
 }
 
-function addBeanWithProperties(res, bean, beanPropName, beanClass, props, alwaysCreateBean) {
+function addBeanWithProperties(res, bean, beanPropName, beanClass, props, createBeanAlthoughNoProps) {
     if (!bean)
         return;
 
@@ -253,36 +362,66 @@ function addBeanWithProperties(res, bean, beanPropName, beanClass, props, always
         }
     }
     
-    if (hasProp) {
+    if (generatorUtils.hasProperty(bean, props)) {
         res.emptyLineIfNeeded();
         res.startBlock('<property name="' + beanPropName + '">');
         res.startBlock('<bean class="' + beanClass + '">');
-        for (i = 0; i < props.length; i++) {
-            addProperty(res, bean, props[i]);
+
+        for (var propName in props) {
+            if (props.hasOwnProperty(propName)) {
+                var setterName = null;
+
+                var descr = props[propName];
+
+                if (descr) {
+                    if (typeof(descr) == 'string') {
+                        var type = descr;
+                    }
+                    else if (typeof(descr) == 'object') {
+                        type = descr.type;
+
+                        setterName = descr.setterName
+                    }
+                }
+
+                if (type == 'list') {
+                    addListProperty(res, bean, propName, setterName);
+                }
+                else {
+                    addProperty(res, bean, propName, setterName);
+                }
+            }
         }
+
         res.endBlock('</bean>');
         res.endBlock('</property>');
     }
-    else if (alwaysCreateBean) {
+    else if (createBeanAlthoughNoProps) {
         res.emptyLineIfNeeded();
         res.line('<property name="' + beanPropName + '">');
         res.line('    <bean class="' + beanClass + '"/>');
         res.line('</property>');
     }
 }
-function addListProperty(res, obj, propName) {
+function addListProperty(res, obj, propName, listType, rowFactory) {
     var val = obj[propName];
 
     if (val && val.length > 0) {
         res.emptyLineIfNeeded();
 
+        if (!listType)
+            listType = 'list';
+
+        if (!rowFactory)
+            rowFactory = function(val) { return '<value>' + escape(val) + '</value>' };
+
         res.startBlock('<property name="' + propName + '">');
-        res.startBlock('<list>');
+        res.startBlock('<' + listType + '>');
 
         for (var i = 0; i < val.length; i++)
-            res.line('<value>' + escape(val[i]) + '</value>');
+            res.line(rowFactory(val[i]));
 
-        res.endBlock('</list>');
+        res.endBlock('</' + listType + '>');
         res.endBlock('</property>');
     }
 }