Author: hannes
Date: 2009-03-31 13:54:21 +0200 (Tue, 31 Mar 2009)
New Revision: 9557
Modified:
helma/helma/trunk/src/helma/objectmodel/db/DbMapping.java
helma/helma/trunk/src/helma/objectmodel/db/Relation.java
helma/helma/trunk/src/helma/scripting/rhino/HopObjectCtor.java
helma/helma/trunk/src/helma/scripting/rhino/RhinoCore.java
Log:
Add static getCollection() method on HopObject constructors to generate collections programmatically and on the fly. Implement limit and offset collection properties for databases that support it (Postgresql + Mysql)
Details at http://dev.helma.org/trac/helma/changeset/9557
Modified: helma/helma/trunk/src/helma/objectmodel/db/DbMapping.java
===================================================================
--- helma/helma/trunk/src/helma/objectmodel/db/DbMapping.java 2009-03-30 13:07:47 UTC (rev 9556)
+++ helma/helma/trunk/src/helma/objectmodel/db/DbMapping.java 2009-03-31 11:54:21 UTC (rev 9557)
@@ -36,7 +36,7 @@
private final String typename;
// properties from where the mapping is read
- private final ResourceProperties props;
+ private final Properties props;
// name of data dbSource to which this mapping writes
private DbSource dbSource;
@@ -147,7 +147,7 @@
/**
* Create a DbMapping from a type.properties property file
*/
- public DbMapping(Application app, String typename, ResourceProperties props) {
+ public DbMapping(Application app, String typename, Properties props) {
this.app = app;
// create a unique instance of the string. This is useful so
// we can compare types just by using == instead of equals.
@@ -169,7 +169,10 @@
* Tell the type manager whether we need update() to be called
*/
public boolean needsUpdate() {
- return props.lastModified() != lastTypeChange;
+ if (props instanceof ResourceProperties) {
+ return ((ResourceProperties) props).lastModified() != lastTypeChange;
+ }
+ return false;
}
/**
@@ -233,7 +236,8 @@
parentInfo = null;
}
- lastTypeChange = props.lastModified();
+ lastTypeChange = props instanceof ResourceProperties ?
+ ((ResourceProperties) props).lastModified() : System.currentTimeMillis();
// see if this prototype extends (inherits from) any other prototype
String extendsProto = props.getProperty("_extends");
@@ -298,13 +302,15 @@
HashMap d2p = new HashMap();
ArrayList joinList = new ArrayList();
- for (Enumeration e = props.keys(); e.hasMoreElements();) {
- String propName = (String) e.nextElement();
+ for (Iterator it = props.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry entry = (Map.Entry) it.next();
try {
+ String propName = (String) entry.getKey();
+
// ignore internal properties (starting with "_") and sub-options (containing a ".")
if (!propName.startsWith("_") && (propName.indexOf(".") < 0)) {
- String dbField = props.getProperty(propName);
+ Object propValue = entry.getValue();
// check if a relation for this propery already exists. If so, reuse it
Relation rel = (Relation) prop2db.get(propName);
@@ -313,7 +319,7 @@
rel = new Relation(propName, this);
}
- rel.update(dbField, props);
+ rel.update(propValue, getSubProperties(propName));
p2d.put(propName, rel);
if ((rel.columnName != null) && rel.isPrimitiveOrReference()) {
@@ -354,7 +360,7 @@
joins = new Relation[joinList.size()];
joins = (Relation[]) joinList.toArray(joins);
- String subnodeMapping = props.getProperty("_children");
+ Object subnodeMapping = props.get("_children");
if (subnodeMapping != null) {
try {
@@ -363,7 +369,7 @@
subRelation = new Relation("_children", this);
}
- subRelation.update(subnodeMapping, props);
+ subRelation.update(subnodeMapping, getSubProperties("_children"));
// if subnodes are accessed via access name or group name,
// the subnode relation is also the property relation.
@@ -396,6 +402,9 @@
*/
private void registerExtension(String extID, String extName) {
// lazy initialization of extensionMap
+ if (extID == null) {
+ return;
+ }
if (extensionMap == null) {
extensionMap = new ResourceProperties();
extensionMap.setIgnoreCase(true);
@@ -1469,10 +1478,31 @@
*
* @return our properties
*/
- public ResourceProperties getProperties() {
+ public Properties getProperties() {
return props;
}
+ public Properties getSubProperties(String prefix) {
+ if (props.get(prefix) instanceof Properties) {
+ return (Properties) props.get(prefix);
+ } else if (props instanceof ResourceProperties) {
+ return ((ResourceProperties) props).getSubProperties(prefix + ".");
+ } else {
+ Properties subprops = new Properties();
+ prefix = prefix + ".";
+ Iterator it = props.entrySet().iterator();
+ int prefixLength = prefix.length();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String key = entry.getKey().toString();
+ if (key.regionMatches(false, 0, prefix, 0, prefixLength)) {
+ subprops.put(key.substring(prefixLength), entry.getValue());
+ }
+ }
+ return subprops;
+ }
+ }
+
/**
* Register a DbMapping that depends on this DbMapping, so that collections of other mapping
* should be reloaded if data on this mapping is updated.
Modified: helma/helma/trunk/src/helma/objectmodel/db/Relation.java
===================================================================
--- helma/helma/trunk/src/helma/objectmodel/db/Relation.java 2009-03-30 13:07:47 UTC (rev 9556)
+++ helma/helma/trunk/src/helma/objectmodel/db/Relation.java 2009-03-31 11:54:21 UTC (rev 9557)
@@ -102,6 +102,7 @@
Vector filterFragments;
Vector filterPropertyRefs;
int maxSize = 0;
+ int offset = 0;
/**
* This constructor makes a copy of an existing relation. Not all fields are copied, just those
@@ -123,6 +124,7 @@
this.additionalTablesJoined = rel.additionalTablesJoined;
this.queryHints = rel.queryHints;
this.maxSize = rel.maxSize;
+ this.offset = rel.offset;
this.constraints = rel.constraints;
this.accessName = rel.accessName;
this.logicalOperator = rel.logicalOperator;
@@ -144,73 +146,53 @@
////////////////////////////////////////////////////////////////////////////////////////////
// parse methods for new file format
////////////////////////////////////////////////////////////////////////////////////////////
- public void update(String desc, ResourceProperties props) {
+ public void update(Object desc, Properties props) {
Application app = ownType.getApplication();
- if ((desc == null) || "".equals(desc.trim())) {
- if (propName != null) {
- reftype = PRIMITIVE;
- columnName = propName;
+ if (desc instanceof Properties || parseDescriptor(desc, props)) {
+ // new style foo.collectionOf = Bar mapping
+ String proto;
+ if (props.containsKey("collection")) {
+ proto = props.getProperty("collection");
+ virtual = !"_children".equalsIgnoreCase(propName);
+ reftype = COLLECTION;
+ } else if (props.containsKey("mountpoint")) {
+ proto = props.getProperty("mountpoint");
+ reftype = COLLECTION;
+ virtual = true;
+ this.prototype = proto;
+ } else if (props.containsKey("object")) {
+ proto = props.getProperty("object");
+ if (reftype != COMPLEX_REFERENCE) {
+ reftype = REFERENCE;
+ }
+ virtual = false;
} else {
- reftype = INVALID;
- columnName = propName;
+ throw new RuntimeException("Invalid property Mapping: " + desc);
}
- } else {
- desc = desc.trim();
- int open = desc.indexOf("(");
- int close = desc.indexOf(")");
+ otherType = app.getDbMapping(proto);
- if ((open > -1) && (close > open)) {
- String ref = desc.substring(0, open).trim();
- String proto = desc.substring(open + 1, close).trim();
+ if (otherType == null) {
+ throw new RuntimeException("DbMapping for " + proto +
+ " not found from " + ownType.getTypeName());
+ }
- if ("collection".equalsIgnoreCase(ref)) {
- virtual = !"_children".equalsIgnoreCase(propName);
- reftype = COLLECTION;
- } else if ("mountpoint".equalsIgnoreCase(ref)) {
- virtual = true;
- reftype = COLLECTION;
- prototype = proto;
- } else if ("object".equalsIgnoreCase(ref)) {
- virtual = false;
- if (reftype != COMPLEX_REFERENCE) {
- reftype = REFERENCE;
- }
- } else {
- throw new RuntimeException("Invalid property Mapping: " + desc);
- }
+ // make sure the type we're referring to is up to date!
+ if (otherType.needsUpdate()) {
+ otherType.update();
+ }
- otherType = app.getDbMapping(proto);
-
- if (otherType == null) {
- throw new RuntimeException("DbMapping for " + proto +
- " not found from " + ownType.getTypeName());
- }
-
- // make sure the type we're referring to is up to date!
- if (otherType.needsUpdate()) {
- otherType.update();
- }
-
- } else {
- virtual = false;
- columnName = desc;
- reftype = PRIMITIVE;
- }
}
- ResourceProperties config = props.getSubProperties(propName + '.');
+ readonly = "true".equalsIgnoreCase(props.getProperty("readonly"));
+ isPrivate = "true".equalsIgnoreCase(props.getProperty("private"));
- readonly = "true".equalsIgnoreCase(config.getProperty("readonly"));
-
- isPrivate = "true".equalsIgnoreCase(config.getProperty("private"));
-
// the following options only apply to object and collection relations
if ((reftype != PRIMITIVE) && (reftype != INVALID)) {
Vector newConstraints = new Vector();
- parseOptions(newConstraints, config);
+ parseOptions(newConstraints, props);
constraints = new Constraint[newConstraints.size()];
newConstraints.copyInto(constraints);
@@ -256,32 +238,84 @@
}
}
- protected void parseOptions(Vector cnst, Properties config) {
- String loading = config.getProperty("loadmode");
+ /**
+ * Converts old style foo = collection(Bar) mapping to new style
+ * foo.collection = Bar mappinng and returns true if a non-primitive mapping
+ * was encountered.
+ * @param value the value of the top level property mapping
+ * @param config the sub-map for this property mapping
+ * @return true if the value describes a valid, non-primitive property mapping
+ */
+ protected boolean parseDescriptor(Object value, Map config) {
+ String desc = value instanceof String ? (String) value : null;
+ if ((desc == null) || "".equals(desc.trim())) {
+ if (propName != null) {
+ reftype = PRIMITIVE;
+ columnName = propName;
+ } else {
+ reftype = INVALID;
+ columnName = propName;
+ }
+ return false;
+ } else {
+ desc = desc.trim();
+
+ int open = desc.indexOf("(");
+ int close = desc.indexOf(")");
+
+ if ((open > -1) && (close > open)) {
+ String ref = desc.substring(0, open).trim();
+ String proto = desc.substring(open + 1, close).trim();
+
+ if ("collection".equalsIgnoreCase(ref)) {
+ config.put("collection", proto);
+ } else if ("mountpoint".equalsIgnoreCase(ref)) {
+ config.put("mountpoint", proto);
+ } else if ("object".equalsIgnoreCase(ref)) {
+ config.put("object", proto);
+ } else {
+ throw new RuntimeException("Invalid property Mapping: " + desc);
+ }
+
+ return true;
+
+ } else {
+ virtual = false;
+ columnName = desc;
+ reftype = PRIMITIVE;
+ return false;
+ }
+ }
+
+ }
+
+ protected void parseOptions(Vector cnst, Properties props) {
+ String loading = props.getProperty("loadmode");
+
aggressiveLoading = (loading != null) &&
"aggressive".equalsIgnoreCase(loading.trim());
- String caching = config.getProperty("cachemode");
+ String caching = props.getProperty("cachemode");
aggressiveCaching = (caching != null) &&
"aggressive".equalsIgnoreCase(caching.trim());
// get order property
- order = config.getProperty("order");
+ order = props.getProperty("order");
if ((order != null) && (order.trim().length() == 0)) {
order = null;
}
// get the criteria(s) for updating this collection
- updateCriteria = config.getProperty("updatecriteria");
+ updateCriteria = props.getProperty("updatecriteria");
// get the autosorting flag
- autoSorted = "auto".equalsIgnoreCase(config.getProperty("sortmode"));
+ autoSorted = "auto".equalsIgnoreCase(props.getProperty("sortmode"));
// get additional filter property
- filter = config.getProperty("filter");
+ filter = props.getProperty("filter");
if (filter != null) {
if (filter.trim().length() == 0) {
@@ -304,7 +338,7 @@
}
// get additional tables
- additionalTables = config.getProperty("filter.additionalTables");
+ additionalTables = props.getProperty("filter.additionalTables");
if (additionalTables != null) {
if (additionalTables.trim().length() == 0) {
@@ -334,36 +368,31 @@
}
// get query hints
- queryHints = config.getProperty("hints");
+ queryHints = props.getProperty("hints");
// get max size of collection
- String max = config.getProperty("maxSize");
-
- if (max != null) {
- try {
- maxSize = Integer.parseInt(max);
- } catch (NumberFormatException nfe) {
- maxSize = 0;
- }
- } else {
- maxSize = 0;
+ maxSize = getIntegerProperty("maxSize", props, 0);
+ if (maxSize == 0) {
+ // use limit as alias for maxSize
+ maxSize = getIntegerProperty("limit", props, 0);
}
+ offset = getIntegerProperty("offset", props, 0);
// get group by property
- groupby = config.getProperty("group");
+ groupby = props.getProperty("group");
if ((groupby != null) && (groupby.trim().length() == 0)) {
groupby = null;
}
if (groupby != null) {
- groupbyOrder = config.getProperty("group.order");
+ groupbyOrder = props.getProperty("group.order");
if ((groupbyOrder != null) && (groupbyOrder.trim().length() == 0)) {
groupbyOrder = null;
}
- groupbyPrototype = config.getProperty("group.prototype");
+ groupbyPrototype = props.getProperty("group.prototype");
if ((groupbyPrototype != null) && (groupbyPrototype.trim().length() == 0)) {
groupbyPrototype = null;
@@ -374,11 +403,11 @@
}
// check if subnode condition should be applied for property relations
- accessName = config.getProperty("accessname");
+ accessName = props.getProperty("accessname");
// parse contstraints
- String local = config.getProperty("local");
- String foreign = config.getProperty("foreign");
+ String local = props.getProperty("local");
+ String foreign = props.getProperty("foreign");
if ((local != null) && (foreign != null)) {
cnst.addElement(new Constraint(local, foreign, false));
@@ -387,8 +416,8 @@
// parse additional contstraints from *.1 to *.9
for (int i=1; i<10; i++) {
- local = config.getProperty("local."+i);
- foreign = config.getProperty("foreign."+i);
+ local = props.getProperty("local."+i);
+ foreign = props.getProperty("foreign."+i);
if ((local != null) && (foreign != null)) {
cnst.addElement(new Constraint(local, foreign, false));
@@ -397,7 +426,7 @@
// parse constraints logic
if (cnst.size() > 1) {
- String logic = config.getProperty("logicalOperator");
+ String logic = props.getProperty("logicalOperator");
if ("and".equalsIgnoreCase(logic)) {
logicalOperator = AND;
} else if ("or".equalsIgnoreCase(logic)) {
@@ -413,13 +442,24 @@
}
+ private int getIntegerProperty(String name, Properties props, int defaultValue) {
+ Object value = props.get(name);
+
+ if (value instanceof Number) {
+ return ((Number) value).intValue();
+ } else if (value instanceof String) {
+ return Integer.parseInt((String) value);
+ }
+ return defaultValue;
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the configuration properties for this relation.
*/
- public ResourceProperties getConfig() {
- return ownType.getProperties().getSubProperties(propName + '.');
+ public Map getConfig() {
+ return ownType.getSubProperties(propName + '.');
}
/**
@@ -875,6 +915,13 @@
q.append(" ORDER BY ").append(order);
}
+ if (maxSize > 0 && !ownType.isOracle()) {
+ q.append(" LIMIT ").append(maxSize);
+ if (offset > 0) {
+ q.append(" OFFSET ").append(offset);
+ }
+ }
+
return q.toString();
}
Modified: helma/helma/trunk/src/helma/scripting/rhino/HopObjectCtor.java
===================================================================
--- helma/helma/trunk/src/helma/scripting/rhino/HopObjectCtor.java 2009-03-30 13:07:47 UTC (rev 9556)
+++ helma/helma/trunk/src/helma/scripting/rhino/HopObjectCtor.java 2009-03-31 11:54:21 UTC (rev 9557)
@@ -17,6 +17,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.util.Properties;
import helma.objectmodel.INode;
import helma.objectmodel.db.DbMapping;
@@ -58,6 +59,7 @@
this.protoProperty = prototype;
addAsConstructor(core.global, prototype);
defineProperty("getById", new GetById(core.global), attr);
+ defineProperty("getCollection", new HopCollection(core.global), attr);
}
/**
@@ -180,4 +182,44 @@
}
+ class HopCollection extends BaseFunction {
+
+ public HopCollection(Scriptable scope) {
+ ScriptRuntime.setFunctionProtoAndParent(this, scope);
+ }
+
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Wrong number of arguments in definePrototype()");
+ }
+ if (!(args[0] instanceof Scriptable)) {
+ throw new IllegalArgumentException("Second argument to HopObject.definePrototype() must be Object");
+ }
+
+ Scriptable desc = (Scriptable) args[0];
+ Properties childmapping = core.scriptableToProperties(desc);
+ if (!childmapping.containsKey("collection")) {
+ // if contained type isn't defined explicitly limit collection to our own type
+ childmapping.put("collection", HopObjectCtor.this.getFunctionName());
+ }
+
+ Node node = new Node("HopQuery", null, core.app.getWrappedNodeManager());
+ Properties props = new Properties();
+ props.put("_children", childmapping);
+ DbMapping dbmap = new DbMapping(core.app, null, props);
+ dbmap.update();
+ node.setDbMapping(dbmap);
+ node.setState(Node.VIRTUAL);
+ return new HopObject("HopQuery", core, node, core.hopObjectProto);
+ }
+
+ public int getArity() {
+ return 1;
+ }
+
+ public int getLength() {
+ return 1;
+ }
+ }
+
}
Modified: helma/helma/trunk/src/helma/scripting/rhino/RhinoCore.java
===================================================================
--- helma/helma/trunk/src/helma/scripting/rhino/RhinoCore.java 2009-03-30 13:07:47 UTC (rev 9556)
+++ helma/helma/trunk/src/helma/scripting/rhino/RhinoCore.java 2009-03-31 11:54:21 UTC (rev 9557)
@@ -23,10 +23,7 @@
import helma.objectmodel.*;
import helma.objectmodel.db.DbMapping;
import helma.scripting.*;
-import helma.util.CacheMap;
-import helma.util.SystemMap;
-import helma.util.WrappedMap;
-import helma.util.WeakCacheMap;
+import helma.util.*;
import org.mozilla.javascript.*;
import org.mozilla.javascript.tools.debugger.ScopeProvider;
@@ -216,7 +213,7 @@
*
* @param prototype the prototype to be created
*/
- private synchronized void initPrototype(Prototype prototype) {
+ protected synchronized TypeInfo initPrototype(Prototype prototype) {
String name = prototype.getName();
String lowerCaseName = prototype.getLowerCaseName();
@@ -236,7 +233,7 @@
} else {
op = new HopObject(name, this);
}
- registerPrototype(prototype, op);
+ type = registerPrototype(prototype, op);
}
// Register a constructor for all types except global.
@@ -250,6 +247,8 @@
app.logError("Error adding ctor for " + name, x);
}
}
+
+ return type;
}
/**
@@ -291,7 +290,7 @@
* @param prototype the prototype spec
* @param type the prototype object info
*/
- private void setParentPrototype(Prototype prototype, TypeInfo type) {
+ protected void setParentPrototype(Prototype prototype, TypeInfo type) {
String name = prototype.getName();
String lowerCaseName = prototype.getLowerCaseName();
@@ -433,7 +432,7 @@
public Map getPrototypeProperties(String protoName) {
TypeInfo type = getPrototypeInfo(protoName);
SystemMap map = new SystemMap();
- Iterator it = type.compiledProperties.iterator();
+ Iterator it = type.compiledProperties.iterator();
while(it.hasNext()) {
Object key = it.next();
if (key instanceof String)
@@ -748,6 +747,26 @@
return href;
}
+
+ Properties scriptableToProperties(Scriptable obj) {
+ Object[] ids = obj.getIds();
+ Properties props = new ResourceProperties(app, null, null, true);
+ for (int i = 0; i < ids.length; i++) {
+ // we ignore non-string keys
+ if (ids[i] instanceof String) {
+ String key = (String) ids[i];
+ Object value = obj.get(key, obj);
+ if (value == Undefined.instance || value == Scriptable.NOT_FOUND) {
+ value = null;
+ } else if (value instanceof Scriptable) {
+ value = scriptableToProperties((Scriptable) value);
+ }
+ props.put(key, value);
+ }
+ }
+ return props;
+ }
+
/**
* Get the RhinoCore instance associated with the current thread, or null
* @return the RhinoCore instance associated with the current thread
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.