diff --git a/src/helma/objectmodel/db/DbMapping.java b/src/helma/objectmodel/db/DbMapping.java index ea9de43a..616cf6ea 100644 --- a/src/helma/objectmodel/db/DbMapping.java +++ b/src/helma/objectmodel/db/DbMapping.java @@ -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. diff --git a/src/helma/objectmodel/db/Relation.java b/src/helma/objectmodel/db/Relation.java index 48b2545b..a7f080f8 100644 --- a/src/helma/objectmodel/db/Relation.java +++ b/src/helma/objectmodel/db/Relation.java @@ -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,73 +146,53 @@ 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)) { - 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); + 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; } - - 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; + } 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(); + } + } - 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(); } diff --git a/src/helma/scripting/rhino/HopObjectCtor.java b/src/helma/scripting/rhino/HopObjectCtor.java index 1f00ce61..5494dd92 100644 --- a/src/helma/scripting/rhino/HopObjectCtor.java +++ b/src/helma/scripting/rhino/HopObjectCtor.java @@ -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; + } + } + } diff --git a/src/helma/scripting/rhino/RhinoCore.java b/src/helma/scripting/rhino/RhinoCore.java index d98fdb61..33d8ca35 100644 --- a/src/helma/scripting/rhino/RhinoCore.java +++ b/src/helma/scripting/rhino/RhinoCore.java @@ -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(); @@ -433,7 +432,7 @@ public final class RhinoCore implements ScopeProvider { 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 @@ 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