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)

This commit is contained in:
hns 2009-03-31 11:54:21 +00:00
parent 0a62df7875
commit a32b4f2c86
4 changed files with 242 additions and 104 deletions

View file

@ -36,7 +36,7 @@ public final class DbMapping {
private final String typename; private final String typename;
// properties from where the mapping is read // properties from where the mapping is read
private final ResourceProperties props; private final Properties props;
// name of data dbSource to which this mapping writes // name of data dbSource to which this mapping writes
private DbSource dbSource; private DbSource dbSource;
@ -147,7 +147,7 @@ public final class DbMapping {
/** /**
* Create a DbMapping from a type.properties property file * 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; this.app = app;
// create a unique instance of the string. This is useful so // create a unique instance of the string. This is useful so
// we can compare types just by using == instead of equals. // we can compare types just by using == instead of equals.
@ -169,7 +169,10 @@ public final class DbMapping {
* Tell the type manager whether we need update() to be called * Tell the type manager whether we need update() to be called
*/ */
public boolean needsUpdate() { public boolean needsUpdate() {
return props.lastModified() != lastTypeChange; if (props instanceof ResourceProperties) {
return ((ResourceProperties) props).lastModified() != lastTypeChange;
}
return false;
} }
/** /**
@ -233,7 +236,8 @@ public final class DbMapping {
parentInfo = null; parentInfo = null;
} }
lastTypeChange = props.lastModified(); lastTypeChange = props instanceof ResourceProperties ?
((ResourceProperties) props).lastModified() : System.currentTimeMillis();
// see if this prototype extends (inherits from) any other prototype // see if this prototype extends (inherits from) any other prototype
String extendsProto = props.getProperty("_extends"); String extendsProto = props.getProperty("_extends");
@ -298,13 +302,15 @@ public final class DbMapping {
HashMap d2p = new HashMap(); HashMap d2p = new HashMap();
ArrayList joinList = new ArrayList(); ArrayList joinList = new ArrayList();
for (Enumeration e = props.keys(); e.hasMoreElements();) { for (Iterator it = props.entrySet().iterator(); it.hasNext(); ) {
String propName = (String) e.nextElement(); Map.Entry entry = (Map.Entry) it.next();
try { try {
String propName = (String) entry.getKey();
// ignore internal properties (starting with "_") and sub-options (containing a ".") // ignore internal properties (starting with "_") and sub-options (containing a ".")
if (!propName.startsWith("_") && (propName.indexOf(".") < 0)) { 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 // check if a relation for this propery already exists. If so, reuse it
Relation rel = (Relation) prop2db.get(propName); Relation rel = (Relation) prop2db.get(propName);
@ -313,7 +319,7 @@ public final class DbMapping {
rel = new Relation(propName, this); rel = new Relation(propName, this);
} }
rel.update(dbField, props); rel.update(propValue, getSubProperties(propName));
p2d.put(propName, rel); p2d.put(propName, rel);
if ((rel.columnName != null) && rel.isPrimitiveOrReference()) { if ((rel.columnName != null) && rel.isPrimitiveOrReference()) {
@ -354,7 +360,7 @@ public final class DbMapping {
joins = new Relation[joinList.size()]; joins = new Relation[joinList.size()];
joins = (Relation[]) joinList.toArray(joins); joins = (Relation[]) joinList.toArray(joins);
String subnodeMapping = props.getProperty("_children"); Object subnodeMapping = props.get("_children");
if (subnodeMapping != null) { if (subnodeMapping != null) {
try { try {
@ -363,7 +369,7 @@ public final class DbMapping {
subRelation = new Relation("_children", this); subRelation = new Relation("_children", this);
} }
subRelation.update(subnodeMapping, props); subRelation.update(subnodeMapping, getSubProperties("_children"));
// if subnodes are accessed via access name or group name, // if subnodes are accessed via access name or group name,
// the subnode relation is also the property relation. // the subnode relation is also the property relation.
@ -396,6 +402,9 @@ public final class DbMapping {
*/ */
private void registerExtension(String extID, String extName) { private void registerExtension(String extID, String extName) {
// lazy initialization of extensionMap // lazy initialization of extensionMap
if (extID == null) {
return;
}
if (extensionMap == null) { if (extensionMap == null) {
extensionMap = new ResourceProperties(); extensionMap = new ResourceProperties();
extensionMap.setIgnoreCase(true); extensionMap.setIgnoreCase(true);
@ -1469,10 +1478,31 @@ public final class DbMapping {
* *
* @return our properties * @return our properties
*/ */
public ResourceProperties getProperties() { public Properties getProperties() {
return props; 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 * Register a DbMapping that depends on this DbMapping, so that collections of other mapping
* should be reloaded if data on this mapping is updated. * should be reloaded if data on this mapping is updated.

View file

@ -102,6 +102,7 @@ public final class Relation {
Vector filterFragments; Vector filterFragments;
Vector filterPropertyRefs; Vector filterPropertyRefs;
int maxSize = 0; int maxSize = 0;
int offset = 0;
/** /**
* This constructor makes a copy of an existing relation. Not all fields are copied, just those * This constructor makes a copy of an existing relation. Not all fields are copied, just those
@ -123,6 +124,7 @@ public final class Relation {
this.additionalTablesJoined = rel.additionalTablesJoined; this.additionalTablesJoined = rel.additionalTablesJoined;
this.queryHints = rel.queryHints; this.queryHints = rel.queryHints;
this.maxSize = rel.maxSize; this.maxSize = rel.maxSize;
this.offset = rel.offset;
this.constraints = rel.constraints; this.constraints = rel.constraints;
this.accessName = rel.accessName; this.accessName = rel.accessName;
this.logicalOperator = rel.logicalOperator; this.logicalOperator = rel.logicalOperator;
@ -144,73 +146,53 @@ public final class Relation {
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
// parse methods for new file format // parse methods for new file format
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
public void update(String desc, ResourceProperties props) { public void update(Object desc, Properties props) {
Application app = ownType.getApplication(); Application app = ownType.getApplication();
if ((desc == null) || "".equals(desc.trim())) { if (desc instanceof Properties || parseDescriptor(desc, props)) {
if (propName != null) { // new style foo.collectionOf = Bar mapping
reftype = PRIMITIVE; String proto;
columnName = propName; if (props.containsKey("collection")) {
} else { proto = props.getProperty("collection");
reftype = INVALID; virtual = !"_children".equalsIgnoreCase(propName);
columnName = propName; reftype = COLLECTION;
} } else if (props.containsKey("mountpoint")) {
} else { proto = props.getProperty("mountpoint");
desc = desc.trim(); reftype = COLLECTION;
virtual = true;
int open = desc.indexOf("("); this.prototype = proto;
int close = desc.indexOf(")"); } else if (props.containsKey("object")) {
proto = props.getProperty("object");
if ((open > -1) && (close > open)) { if (reftype != COMPLEX_REFERENCE) {
String ref = desc.substring(0, open).trim(); reftype = REFERENCE;
String proto = desc.substring(open + 1, close).trim();
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);
} }
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; virtual = false;
columnName = desc; } else {
reftype = PRIMITIVE; throw new RuntimeException("Invalid property Mapping: " + desc);
} }
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();
}
} }
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 // the following options only apply to object and collection relations
if ((reftype != PRIMITIVE) && (reftype != INVALID)) { if ((reftype != PRIMITIVE) && (reftype != INVALID)) {
Vector newConstraints = new Vector(); Vector newConstraints = new Vector();
parseOptions(newConstraints, config); parseOptions(newConstraints, props);
constraints = new Constraint[newConstraints.size()]; constraints = new Constraint[newConstraints.size()];
newConstraints.copyInto(constraints); newConstraints.copyInto(constraints);
@ -256,32 +238,84 @@ public final class Relation {
} }
} }
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) && aggressiveLoading = (loading != null) &&
"aggressive".equalsIgnoreCase(loading.trim()); "aggressive".equalsIgnoreCase(loading.trim());
String caching = config.getProperty("cachemode"); String caching = props.getProperty("cachemode");
aggressiveCaching = (caching != null) && aggressiveCaching = (caching != null) &&
"aggressive".equalsIgnoreCase(caching.trim()); "aggressive".equalsIgnoreCase(caching.trim());
// get order property // get order property
order = config.getProperty("order"); order = props.getProperty("order");
if ((order != null) && (order.trim().length() == 0)) { if ((order != null) && (order.trim().length() == 0)) {
order = null; order = null;
} }
// get the criteria(s) for updating this collection // get the criteria(s) for updating this collection
updateCriteria = config.getProperty("updatecriteria"); updateCriteria = props.getProperty("updatecriteria");
// get the autosorting flag // get the autosorting flag
autoSorted = "auto".equalsIgnoreCase(config.getProperty("sortmode")); autoSorted = "auto".equalsIgnoreCase(props.getProperty("sortmode"));
// get additional filter property // get additional filter property
filter = config.getProperty("filter"); filter = props.getProperty("filter");
if (filter != null) { if (filter != null) {
if (filter.trim().length() == 0) { if (filter.trim().length() == 0) {
@ -304,7 +338,7 @@ public final class Relation {
} }
// get additional tables // get additional tables
additionalTables = config.getProperty("filter.additionalTables"); additionalTables = props.getProperty("filter.additionalTables");
if (additionalTables != null) { if (additionalTables != null) {
if (additionalTables.trim().length() == 0) { if (additionalTables.trim().length() == 0) {
@ -334,36 +368,31 @@ public final class Relation {
} }
// get query hints // get query hints
queryHints = config.getProperty("hints"); queryHints = props.getProperty("hints");
// get max size of collection // get max size of collection
String max = config.getProperty("maxSize"); maxSize = getIntegerProperty("maxSize", props, 0);
if (maxSize == 0) {
if (max != null) { // use limit as alias for maxSize
try { maxSize = getIntegerProperty("limit", props, 0);
maxSize = Integer.parseInt(max);
} catch (NumberFormatException nfe) {
maxSize = 0;
}
} else {
maxSize = 0;
} }
offset = getIntegerProperty("offset", props, 0);
// get group by property // get group by property
groupby = config.getProperty("group"); groupby = props.getProperty("group");
if ((groupby != null) && (groupby.trim().length() == 0)) { if ((groupby != null) && (groupby.trim().length() == 0)) {
groupby = null; groupby = null;
} }
if (groupby != null) { if (groupby != null) {
groupbyOrder = config.getProperty("group.order"); groupbyOrder = props.getProperty("group.order");
if ((groupbyOrder != null) && (groupbyOrder.trim().length() == 0)) { if ((groupbyOrder != null) && (groupbyOrder.trim().length() == 0)) {
groupbyOrder = null; groupbyOrder = null;
} }
groupbyPrototype = config.getProperty("group.prototype"); groupbyPrototype = props.getProperty("group.prototype");
if ((groupbyPrototype != null) && (groupbyPrototype.trim().length() == 0)) { if ((groupbyPrototype != null) && (groupbyPrototype.trim().length() == 0)) {
groupbyPrototype = null; groupbyPrototype = null;
@ -374,11 +403,11 @@ public final class Relation {
} }
// check if subnode condition should be applied for property relations // check if subnode condition should be applied for property relations
accessName = config.getProperty("accessname"); accessName = props.getProperty("accessname");
// parse contstraints // parse contstraints
String local = config.getProperty("local"); String local = props.getProperty("local");
String foreign = config.getProperty("foreign"); String foreign = props.getProperty("foreign");
if ((local != null) && (foreign != null)) { if ((local != null) && (foreign != null)) {
cnst.addElement(new Constraint(local, foreign, false)); cnst.addElement(new Constraint(local, foreign, false));
@ -387,8 +416,8 @@ public final class Relation {
// parse additional contstraints from *.1 to *.9 // parse additional contstraints from *.1 to *.9
for (int i=1; i<10; i++) { for (int i=1; i<10; i++) {
local = config.getProperty("local."+i); local = props.getProperty("local."+i);
foreign = config.getProperty("foreign."+i); foreign = props.getProperty("foreign."+i);
if ((local != null) && (foreign != null)) { if ((local != null) && (foreign != null)) {
cnst.addElement(new Constraint(local, foreign, false)); cnst.addElement(new Constraint(local, foreign, false));
@ -397,7 +426,7 @@ public final class Relation {
// parse constraints logic // parse constraints logic
if (cnst.size() > 1) { if (cnst.size() > 1) {
String logic = config.getProperty("logicalOperator"); String logic = props.getProperty("logicalOperator");
if ("and".equalsIgnoreCase(logic)) { if ("and".equalsIgnoreCase(logic)) {
logicalOperator = AND; logicalOperator = AND;
} else if ("or".equalsIgnoreCase(logic)) { } else if ("or".equalsIgnoreCase(logic)) {
@ -413,13 +442,24 @@ public final class Relation {
} }
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. * Get the configuration properties for this relation.
*/ */
public ResourceProperties getConfig() { public Map getConfig() {
return ownType.getProperties().getSubProperties(propName + '.'); return ownType.getSubProperties(propName + '.');
} }
/** /**
@ -875,6 +915,13 @@ public final class Relation {
q.append(" ORDER BY ").append(order); 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(); return q.toString();
} }

View file

@ -17,6 +17,7 @@ package helma.scripting.rhino;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Properties;
import helma.objectmodel.INode; import helma.objectmodel.INode;
import helma.objectmodel.db.DbMapping; import helma.objectmodel.db.DbMapping;
@ -58,6 +59,7 @@ public class HopObjectCtor extends FunctionObject {
this.protoProperty = prototype; this.protoProperty = prototype;
addAsConstructor(core.global, prototype); addAsConstructor(core.global, prototype);
defineProperty("getById", new GetById(core.global), attr); defineProperty("getById", new GetById(core.global), attr);
defineProperty("getCollection", new HopCollection(core.global), attr);
} }
/** /**
@ -180,4 +182,44 @@ public class HopObjectCtor extends FunctionObject {
} }
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;
}
}
} }

View file

@ -23,10 +23,7 @@ import helma.framework.repository.Resource;
import helma.objectmodel.*; import helma.objectmodel.*;
import helma.objectmodel.db.DbMapping; import helma.objectmodel.db.DbMapping;
import helma.scripting.*; import helma.scripting.*;
import helma.util.CacheMap; import helma.util.*;
import helma.util.SystemMap;
import helma.util.WrappedMap;
import helma.util.WeakCacheMap;
import org.mozilla.javascript.*; import org.mozilla.javascript.*;
import org.mozilla.javascript.tools.debugger.ScopeProvider; import org.mozilla.javascript.tools.debugger.ScopeProvider;
@ -216,7 +213,7 @@ public final class RhinoCore implements ScopeProvider {
* *
* @param prototype the prototype to be created * @param prototype the prototype to be created
*/ */
private synchronized void initPrototype(Prototype prototype) { protected synchronized TypeInfo initPrototype(Prototype prototype) {
String name = prototype.getName(); String name = prototype.getName();
String lowerCaseName = prototype.getLowerCaseName(); String lowerCaseName = prototype.getLowerCaseName();
@ -236,7 +233,7 @@ public final class RhinoCore implements ScopeProvider {
} else { } else {
op = new HopObject(name, this); op = new HopObject(name, this);
} }
registerPrototype(prototype, op); type = registerPrototype(prototype, op);
} }
// Register a constructor for all types except global. // Register a constructor for all types except global.
@ -250,6 +247,8 @@ public final class RhinoCore implements ScopeProvider {
app.logError("Error adding ctor for " + name, x); app.logError("Error adding ctor for " + name, x);
} }
} }
return type;
} }
/** /**
@ -291,7 +290,7 @@ public final class RhinoCore implements ScopeProvider {
* @param prototype the prototype spec * @param prototype the prototype spec
* @param type the prototype object info * @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 name = prototype.getName();
String lowerCaseName = prototype.getLowerCaseName(); String lowerCaseName = prototype.getLowerCaseName();
@ -433,7 +432,7 @@ public final class RhinoCore implements ScopeProvider {
public Map getPrototypeProperties(String protoName) { public Map getPrototypeProperties(String protoName) {
TypeInfo type = getPrototypeInfo(protoName); TypeInfo type = getPrototypeInfo(protoName);
SystemMap map = new SystemMap(); SystemMap map = new SystemMap();
Iterator it = type.compiledProperties.iterator(); Iterator it = type.compiledProperties.iterator();
while(it.hasNext()) { while(it.hasNext()) {
Object key = it.next(); Object key = it.next();
if (key instanceof String) if (key instanceof String)
@ -748,6 +747,26 @@ public final class RhinoCore implements ScopeProvider {
return href; 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 * Get the RhinoCore instance associated with the current thread, or null
* @return the RhinoCore instance associated with the current thread * @return the RhinoCore instance associated with the current thread