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;
// 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 @@ public final class DbMapping {
/**
* 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 @@ public final class DbMapping {
* 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 @@ public final class DbMapping {
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 @@ public final class DbMapping {
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 @@ public final class DbMapping {
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 @@ public final class DbMapping {
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 @@ public final class DbMapping {
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 @@ public final class DbMapping {
*/
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 @@ public final class DbMapping {
*
* @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.

View file

@ -102,6 +102,7 @@ public final class Relation {
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 @@ public final class Relation {
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,39 +146,27 @@ public final class Relation {
////////////////////////////////////////////////////////////////////////////////////////////
// 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;
} else {
reftype = INVALID;
columnName = propName;
}
} 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)) {
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 ("mountpoint".equalsIgnoreCase(ref)) {
virtual = true;
} else if (props.containsKey("mountpoint")) {
proto = props.getProperty("mountpoint");
reftype = COLLECTION;
prototype = proto;
} else if ("object".equalsIgnoreCase(ref)) {
virtual = false;
virtual = true;
this.prototype = proto;
} else if (props.containsKey("object")) {
proto = props.getProperty("object");
if (reftype != COMPLEX_REFERENCE) {
reftype = REFERENCE;
}
virtual = false;
} else {
throw new RuntimeException("Invalid property Mapping: " + desc);
}
@ -193,24 +183,16 @@ public final class Relation {
otherType.update();
}
} else {
virtual = false;
columnName = desc;
reftype = PRIMITIVE;
}
}
ResourceProperties config = props.getSubProperties(propName + '.');
readonly = "true".equalsIgnoreCase(config.getProperty("readonly"));
isPrivate = "true".equalsIgnoreCase(config.getProperty("private"));
readonly = "true".equalsIgnoreCase(props.getProperty("readonly"));
isPrivate = "true".equalsIgnoreCase(props.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 @@ 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) &&
"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 @@ public final class Relation {
}
// 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 @@ public final class Relation {
}
// 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 @@ public final class Relation {
}
// 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 @@ public final class Relation {
// 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 @@ public final class Relation {
// 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 @@ 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.
*/
public ResourceProperties getConfig() {
return ownType.getProperties().getSubProperties(propName + '.');
public Map getConfig() {
return ownType.getSubProperties(propName + '.');
}
/**
@ -875,6 +915,13 @@ public final class Relation {
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();
}

View file

@ -17,6 +17,7 @@ package helma.scripting.rhino;
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 @@ public class HopObjectCtor extends FunctionObject {
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 @@ 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.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 @@ public final class RhinoCore implements ScopeProvider {
*
* @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 @@ public final class RhinoCore implements ScopeProvider {
} 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 @@ public final class RhinoCore implements ScopeProvider {
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 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();
@ -748,6 +747,26 @@ public final class RhinoCore implements ScopeProvider {
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