* Implement bug #516
* Fix bug #515 * Some refactoring in helma.objectmodel.db
This commit is contained in:
parent
d8f5446d01
commit
1091d34c77
7 changed files with 319 additions and 196 deletions
|
@ -97,7 +97,7 @@ public final class DbColumn {
|
|||
// Note: not sure if check for primitive or reference relation is really
|
||||
// needed, but we did it before, so we leave it in for safety.
|
||||
return isId || isPrototype || isName ||
|
||||
(relation != null && (relation.isPrimitive() || relation.isReference()));
|
||||
(relation != null && relation.isPrimitiveOrReference());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,8 +62,8 @@ public final class DbMapping {
|
|||
private HashMap prop2db;
|
||||
|
||||
// Map of db columns to Relations objects.
|
||||
// Case insensitive, keys are stored in upper case so
|
||||
// lookups must do a toUpperCase().
|
||||
// Case insensitive, keys are stored in lower case so
|
||||
// lookups must do a toLowerCase().
|
||||
private HashMap db2prop;
|
||||
|
||||
// list of columns to fetch from db
|
||||
|
@ -303,24 +303,21 @@ public final class DbMapping {
|
|||
// (ResourceProperties now preserve key capitalization!)
|
||||
p2d.put(propName.toLowerCase(), rel);
|
||||
|
||||
if ((rel.columnName != null) &&
|
||||
((rel.reftype == Relation.PRIMITIVE) ||
|
||||
(rel.reftype == Relation.REFERENCE))) {
|
||||
Relation old = (Relation) d2p.put(rel.columnName.toUpperCase(), rel);
|
||||
if ((rel.columnName != null) && rel.isPrimitiveOrReference()) {
|
||||
Relation old = (Relation) d2p.put(rel.columnName.toLowerCase(), rel);
|
||||
// check if we're overwriting another relation
|
||||
// if so, primitive relations get precendence to references
|
||||
if (old != null) {
|
||||
app.logEvent("*** Duplicate mapping for "+typename+"."+rel.columnName);
|
||||
if (old.reftype == Relation.PRIMITIVE) {
|
||||
d2p.put(old.columnName.toUpperCase(), old);
|
||||
app.logEvent("*** Duplicate mapping for " + typename + "." + rel.columnName);
|
||||
if (old.isPrimitive()) {
|
||||
d2p.put(old.columnName.toLowerCase(), old);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if a reference is aggressively fetched
|
||||
if ((rel.reftype == Relation.REFERENCE ||
|
||||
rel.reftype == Relation.COMPLEX_REFERENCE) &&
|
||||
rel.aggressiveLoading) {
|
||||
if (rel.aggressiveLoading &&
|
||||
(rel.isReference() || rel.isComplexReference())) {
|
||||
joinList.add(rel);
|
||||
}
|
||||
|
||||
|
@ -413,6 +410,9 @@ public final class DbMapping {
|
|||
return parentMapping.getPrototypeName(id);
|
||||
}
|
||||
// fallback to base-prototype if the proto isn't recogniced
|
||||
if (id == null) {
|
||||
return typename;
|
||||
}
|
||||
return extensionMap.getProperty(id, typename);
|
||||
}
|
||||
|
||||
|
@ -581,7 +581,7 @@ public final class DbMapping {
|
|||
columnName = columnName.substring(open + 1, close);
|
||||
}
|
||||
|
||||
return _columnNameToProperty(columnName.toUpperCase());
|
||||
return _columnNameToProperty(columnName.toLowerCase());
|
||||
}
|
||||
|
||||
private String _columnNameToProperty(final String columnName) {
|
||||
|
@ -591,9 +591,7 @@ public final class DbMapping {
|
|||
return parentMapping._columnNameToProperty(columnName);
|
||||
}
|
||||
|
||||
if ((rel != null) &&
|
||||
((rel.reftype == Relation.PRIMITIVE) ||
|
||||
(rel.reftype == Relation.REFERENCE))) {
|
||||
if ((rel != null) && rel.isPrimitiveOrReference()) {
|
||||
return rel.propName;
|
||||
}
|
||||
|
||||
|
@ -601,15 +599,16 @@ public final class DbMapping {
|
|||
}
|
||||
|
||||
/**
|
||||
* Translate an object property name to a database column name according to this mapping.
|
||||
* Translate an object property name to a database column name according
|
||||
* to this mapping. If no mapping is found, the property name is returned,
|
||||
* assuming property and column names are equal.
|
||||
*/
|
||||
public String propertyToColumnName(String propName) {
|
||||
if (propName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// FIXME: prop2db stores keys in lower case, because it gets them
|
||||
// from a SystemProperties object which converts keys to lower case.
|
||||
// prop2db stores keys in lower case
|
||||
return _propertyToColumnName(propName.toLowerCase());
|
||||
}
|
||||
|
||||
|
@ -620,9 +619,7 @@ public final class DbMapping {
|
|||
return parentMapping._propertyToColumnName(propName);
|
||||
}
|
||||
|
||||
if ((rel != null) &&
|
||||
((rel.reftype == Relation.PRIMITIVE) ||
|
||||
(rel.reftype == Relation.REFERENCE))) {
|
||||
if ((rel != null) && (rel.isPrimitiveOrReference())) {
|
||||
return rel.columnName;
|
||||
}
|
||||
|
||||
|
@ -637,7 +634,7 @@ public final class DbMapping {
|
|||
return null;
|
||||
}
|
||||
|
||||
return _columnNameToRelation(columnName.toUpperCase());
|
||||
return _columnNameToRelation(columnName.toLowerCase());
|
||||
}
|
||||
|
||||
private Relation _columnNameToRelation(final String columnName) {
|
||||
|
@ -702,28 +699,6 @@ public final class DbMapping {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param propname ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public DbMapping getExactPropertyMapping(String propname) {
|
||||
Relation rel = getExactPropertyRelation(propname);
|
||||
|
||||
if (rel != null) {
|
||||
// if this is a virtual node, it doesn't have a dbmapping
|
||||
if (rel.virtual && (rel.prototype == null)) {
|
||||
return null;
|
||||
} else {
|
||||
return rel.otherType;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
|
|
|
@ -46,11 +46,31 @@ public final class MultiKey implements Key, Serializable {
|
|||
|
||||
|
||||
/**
|
||||
* make a key for a persistent Object, describing its datasource and key parts.
|
||||
* Make a key for a persistent Object, describing its datasource and key parts.
|
||||
*/
|
||||
public MultiKey(DbMapping dbmap, Map parts) {
|
||||
this.parts = parts;
|
||||
this.storageName = (dbmap == null) ? null : dbmap.getStorageTypeName();
|
||||
this.storageName = getStorageNameFromParts(dbmap, parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual dbmapping prototype name out of the parts map if possible.
|
||||
* This is necessary to implement references to unspecified prototype targets.
|
||||
* @param dbmap the nominal/static dbmapping
|
||||
* @param parts the parts map
|
||||
* @return the actual dbmapping name
|
||||
*/
|
||||
private String getStorageNameFromParts(DbMapping dbmap, Map parts) {
|
||||
if (dbmap == null)
|
||||
return null;
|
||||
String protoName = (String) parts.get("$prototype");
|
||||
if (protoName != null) {
|
||||
DbMapping dynamap = dbmap.app.getDbMapping(protoName);
|
||||
if (dynamap != null) {
|
||||
return (dynamap.getStorageTypeName());
|
||||
}
|
||||
}
|
||||
return dbmap.getStorageTypeName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package helma.objectmodel.db;
|
||||
|
||||
import helma.framework.IPathElement;
|
||||
import helma.framework.core.RequestEvaluator;
|
||||
import helma.framework.core.Application;
|
||||
import helma.objectmodel.ConcurrencyException;
|
||||
import helma.objectmodel.INode;
|
||||
import helma.objectmodel.IProperty;
|
||||
|
@ -761,9 +763,7 @@ public final class Node implements INode, Serializable {
|
|||
// see if there is an explicit relation defined for this parent info
|
||||
// we only try to fetch a node if an explicit relation is specified for the prop name
|
||||
Relation rel = dbmap.propertyToRelation(pinfo.propname);
|
||||
|
||||
if ((rel != null) && (rel.reftype == Relation.REFERENCE ||
|
||||
rel.reftype == Relation.COMPLEX_REFERENCE)) {
|
||||
if ((rel != null) && (rel.isReference() || rel.isComplexReference())) {
|
||||
pn = (Node) getNode(pinfo.propname);
|
||||
}
|
||||
|
||||
|
@ -776,10 +776,17 @@ public final class Node implements INode, Serializable {
|
|||
if (pn != null) {
|
||||
// see if dbmapping specifies anonymity for this node
|
||||
if (pinfo.virtualname != null) {
|
||||
pn = (Node) pn.getNode(pinfo.virtualname);
|
||||
if (pn == null)
|
||||
nmgr.nmgr.app.logError("Error: Can't retrieve parent node " +
|
||||
Node pn2 = (Node) pn.getNode(pinfo.virtualname);
|
||||
if (pn2 == null) {
|
||||
getApp().logError("Error: Can't retrieve parent node " +
|
||||
pinfo + " for " + this);
|
||||
} else if (pn2.equals(this)) {
|
||||
setParent(pn);
|
||||
name = pinfo.virtualname;
|
||||
anonymous = false;
|
||||
return pn;
|
||||
}
|
||||
pn = pn2;
|
||||
}
|
||||
|
||||
DbMapping dbm = (pn == null) ? null : pn.getDbMapping();
|
||||
|
@ -798,7 +805,7 @@ public final class Node implements INode, Serializable {
|
|||
return pn;
|
||||
}
|
||||
} catch (Exception x) {
|
||||
nmgr.nmgr.app.logError("Error retrieving parent node " +
|
||||
getApp().logError("Error retrieving parent node " +
|
||||
pinfo + " for " + this, x);
|
||||
}
|
||||
}
|
||||
|
@ -810,8 +817,8 @@ public final class Node implements INode, Serializable {
|
|||
}
|
||||
}
|
||||
if (parentHandle == null && !nmgr.isRootNode(this) && state != TRANSIENT) {
|
||||
nmgr.nmgr.app.logEvent("*** Couldn't resolve parent for " + this);
|
||||
nmgr.nmgr.app.logEvent("*** Please check _parent info in type.properties!");
|
||||
getApp().logEvent("*** Couldn't resolve parent for " + this);
|
||||
getApp().logEvent("*** Please check _parent info in type.properties!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1317,7 +1324,8 @@ public final class Node implements INode, Serializable {
|
|||
|
||||
/**
|
||||
* "Locally" remove a subnode from the subnodes table.
|
||||
* The logical stuff necessary for keeping data consistent is done in removeNode().
|
||||
* The logical stuff necessary for keeping data consistent is done in
|
||||
* {@link #removeNode(INode)}.
|
||||
*/
|
||||
protected void releaseNode(Node node) {
|
||||
INode parent = node.getParent();
|
||||
|
@ -1325,35 +1333,40 @@ public final class Node implements INode, Serializable {
|
|||
checkWriteLock();
|
||||
node.checkWriteLock();
|
||||
|
||||
boolean removed = false;
|
||||
|
||||
// load subnodes in case they haven't been loaded.
|
||||
// this is to prevent subsequent access to reload the
|
||||
// index which would potentially still contain the removed child
|
||||
loadNodes();
|
||||
|
||||
if (subnodes != null) {
|
||||
boolean removed = false;
|
||||
synchronized (subnodes) {
|
||||
removed = subnodes.remove(node.getHandle());
|
||||
}
|
||||
if (removed) {
|
||||
registerSubnodeChange();
|
||||
}
|
||||
}
|
||||
|
||||
if (removed)
|
||||
registerSubnodeChange();
|
||||
|
||||
// check if subnodes are also accessed as properties. If so, also unset the property
|
||||
if ((dbmap != null) && (node.dbmap != null)) {
|
||||
Relation prel = dbmap.getSubnodeRelation();
|
||||
|
||||
if ((prel != null) && (prel.accessName != null)) {
|
||||
Relation localrel = node.dbmap.columnNameToRelation(prel.accessName);
|
||||
if (prel != null) {
|
||||
if (prel.accessName != null) {
|
||||
Relation localrel = node.dbmap.columnNameToRelation(prel.accessName);
|
||||
|
||||
// if no relation from db column to prop name is found, assume that both are equal
|
||||
String propname = (localrel == null) ? prel.accessName : localrel.propName;
|
||||
String prop = node.getString(propname);
|
||||
// if no relation from db column to prop name is found, assume that both are equal
|
||||
String propname = (localrel == null) ? prel.accessName : localrel.propName;
|
||||
String prop = node.getString(propname);
|
||||
|
||||
if ((prop != null) && (getNode(prop) == node)) {
|
||||
unset(prop);
|
||||
if ((prop != null) && (getNode(prop) == node)) {
|
||||
unset(prop);
|
||||
}
|
||||
}
|
||||
if (prel.countConstraints() > 1) {
|
||||
prel.unsetConstraints(this, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1370,7 +1383,6 @@ public final class Node implements INode, Serializable {
|
|||
|
||||
lastmodified = System.currentTimeMillis();
|
||||
|
||||
// nmgr.logEvent ("released node "+node +" from "+this+" oldobj = "+what);
|
||||
if (state == CLEAN) {
|
||||
markAs(MODIFIED);
|
||||
}
|
||||
|
@ -2276,12 +2288,15 @@ public final class Node implements INode, Serializable {
|
|||
public void setNode(String propname, INode value) {
|
||||
// nmgr.logEvent ("setting node prop");
|
||||
// check if types match, otherwise throw exception
|
||||
DbMapping nmap = (dbmap == null) ? null : dbmap.getExactPropertyMapping(propname);
|
||||
Relation rel = (dbmap == null) ?
|
||||
null : dbmap.getExactPropertyRelation(propname);
|
||||
DbMapping nmap = (rel == null) ? null : rel.getPropertyMapping();
|
||||
DbMapping vmap = value.getDbMapping();
|
||||
|
||||
if ((nmap != null) && (nmap != value.getDbMapping())) {
|
||||
if (value.getDbMapping() == null) {
|
||||
if ((nmap != null) && (nmap != vmap)) {
|
||||
if (vmap == null) {
|
||||
value.setDbMapping(nmap);
|
||||
} else if (!nmap.isStorageCompatible(value.getDbMapping())) {
|
||||
} else if (!nmap.isStorageCompatible(vmap) && !rel.isComplexReference()) {
|
||||
throw new RuntimeException("Can't set " + propname +
|
||||
" to object with prototype " +
|
||||
value.getPrototype() + ", was expecting " +
|
||||
|
@ -2333,7 +2348,10 @@ public final class Node implements INode, Serializable {
|
|||
|
||||
String p2 = propname.toLowerCase();
|
||||
|
||||
Relation rel = (dbmap == null) ? null : dbmap.getPropertyRelation(propname);
|
||||
if (rel == null && dbmap != null) {
|
||||
// widen relation to non-exact (collection) mapping
|
||||
rel = dbmap.getPropertyRelation(propname);
|
||||
}
|
||||
|
||||
if (rel != null && (rel.countConstraints() > 1 || rel.isComplexReference())) {
|
||||
rel.setConstraints(this, n);
|
||||
|
@ -2367,7 +2385,7 @@ public final class Node implements INode, Serializable {
|
|||
prop.setNodeValue(n);
|
||||
|
||||
if ((rel == null) ||
|
||||
rel.reftype == Relation.REFERENCE ||
|
||||
rel.isReference() ||
|
||||
state == TRANSIENT ||
|
||||
rel.otherType == null ||
|
||||
!rel.otherType.isRelational()) {
|
||||
|
@ -2417,20 +2435,19 @@ public final class Node implements INode, Serializable {
|
|||
* specified via property relation.
|
||||
*/
|
||||
public void unset(String propname) {
|
||||
if (propMap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// if node is relational, leave a null property so that it is
|
||||
// updated in the DB. Otherwise, remove the property.
|
||||
Property p;
|
||||
Property p = null;
|
||||
boolean relational = (dbmap != null) && dbmap.isRelational();
|
||||
|
||||
if (relational) {
|
||||
p = (Property) propMap.get(propname.toLowerCase());
|
||||
} else {
|
||||
p = (Property) propMap.remove(propname.toLowerCase());
|
||||
if (propMap != null) {
|
||||
if (relational) {
|
||||
p = (Property) propMap.get(propname.toLowerCase());
|
||||
} else {
|
||||
p = (Property) propMap.remove(propname.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
if (p != null) {
|
||||
|
@ -2456,7 +2473,8 @@ public final class Node implements INode, Serializable {
|
|||
rel.unsetConstraints(this, p.getNodeValue());
|
||||
}
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
} catch (Exception x) {
|
||||
getApp().logError("Error unsetting property", x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2479,11 +2497,23 @@ public final class Node implements INode, Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
* Return a string representation for this node. This tries to call the
|
||||
* javascript implemented toString() if it is defined.
|
||||
* @return a string representing this node.
|
||||
*/
|
||||
public String toString() {
|
||||
try {
|
||||
// We need to reach deap into helma.framework.core to invoke onInit(),
|
||||
// but the functionality is really worth it.
|
||||
RequestEvaluator reval = getApp().getCurrentRequestEvaluator();
|
||||
if (reval != null) {
|
||||
Object str = reval.invokeDirectFunction(this, "toString", RequestEvaluator.EMPTY_ARGS);
|
||||
if (str instanceof String)
|
||||
return (String) str;
|
||||
}
|
||||
} catch (Exception x) {
|
||||
// fall back to default representation
|
||||
}
|
||||
return "HopObject " + name;
|
||||
}
|
||||
|
||||
|
@ -2670,11 +2700,19 @@ public final class Node implements INode, Serializable {
|
|||
// FIXME: what do we do if this.dbmap is null
|
||||
if (this.dbmap == null) {
|
||||
throw new RuntimeException (this + " doesn't have a DbMapping");
|
||||
}
|
||||
}
|
||||
Relation rel = this.dbmap.getSubnodeRelation();
|
||||
synchronized (this) {
|
||||
lastSubnodeFetch = System.currentTimeMillis();
|
||||
return this.nmgr.updateSubnodeList(this, rel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application this node belongs to.
|
||||
* @return the app we belong to
|
||||
*/
|
||||
private Application getApp() {
|
||||
return nmgr.nmgr.app;
|
||||
}
|
||||
}
|
|
@ -112,7 +112,7 @@ public final class NodeManager {
|
|||
* Checks if the given node is the application's root node.
|
||||
*/
|
||||
public boolean isRootNode(Node node) {
|
||||
return app.getRootId().equals(node.getID()) &&
|
||||
return node.getState() != Node.TRANSIENT && app.getRootId().equals(node.getID()) &&
|
||||
DbMapping.areStorageCompatible(app.getRootMapping(), node.getDbMapping());
|
||||
}
|
||||
|
||||
|
@ -215,11 +215,12 @@ public final class NodeManager {
|
|||
Transactor tx = (Transactor) Thread.currentThread();
|
||||
|
||||
Key key;
|
||||
|
||||
DbMapping otherDbm = rel == null ? null : rel.otherType;
|
||||
// check what kind of object we're looking for and make an apropriate key
|
||||
if (rel.isComplexReference()) {
|
||||
// a key for a complex reference
|
||||
key = new MultiKey(rel.otherType, rel.getKeyParts(home));
|
||||
otherDbm = app.getDbMapping(key.getStorageName());
|
||||
} else if (rel.createOnDemand()) {
|
||||
// a key for a virtually defined object that's never actually stored in the db
|
||||
// or a key for an object that represents subobjects grouped by some property,
|
||||
|
@ -275,7 +276,7 @@ public final class NodeManager {
|
|||
// The requested node isn't in the shared cache.
|
||||
// Synchronize with key to make sure only one version is fetched
|
||||
// from the database.
|
||||
node = getNodeByRelation(tx.txn, home, kstr, rel);
|
||||
node = getNodeByRelation(tx.txn, home, kstr, rel, otherDbm);
|
||||
|
||||
if (node != null) {
|
||||
Node newNode = node;
|
||||
|
@ -433,7 +434,7 @@ public final class NodeManager {
|
|||
* Insert a node into a different (relational) database than its default one.
|
||||
*/
|
||||
public void exportNode(Node node, DbSource dbs)
|
||||
throws IOException, SQLException, ClassNotFoundException {
|
||||
throws SQLException, ClassNotFoundException {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("Node can't be null in exportNode");
|
||||
}
|
||||
|
@ -453,7 +454,7 @@ public final class NodeManager {
|
|||
* Insert a node into a different (relational) database than its default one.
|
||||
*/
|
||||
public void exportNode(Node node, DbMapping dbm)
|
||||
throws IOException, SQLException, ClassNotFoundException {
|
||||
throws SQLException, ClassNotFoundException {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("Node can't be null in exportNode");
|
||||
}
|
||||
|
@ -593,7 +594,7 @@ public final class NodeManager {
|
|||
|
||||
// skip readonly, virtual and collection relations
|
||||
if ((rel == null) || rel.readonly || rel.virtual ||
|
||||
(!rel.isReference() && !rel.isPrimitive())) {
|
||||
(!rel.isPrimitiveOrReference())) {
|
||||
// null out property so we don't consider it later
|
||||
props[i] = null;
|
||||
continue;
|
||||
|
@ -1568,7 +1569,7 @@ public final class NodeManager {
|
|||
return node;
|
||||
}
|
||||
|
||||
private Node getNodeByRelation(ITransaction txn, Node home, String kstr, Relation rel)
|
||||
private Node getNodeByRelation(ITransaction txn, Node home, String kstr, Relation rel, DbMapping dbm)
|
||||
throws Exception {
|
||||
Node node = null;
|
||||
|
||||
|
@ -1582,25 +1583,22 @@ public final class NodeManager {
|
|||
// set prototype and dbmapping on the newly created virtual/collection node
|
||||
node.setPrototype(rel.prototype);
|
||||
node.setDbMapping(rel.getVirtualMapping());
|
||||
} else if ((rel != null) && (rel.groupby != null)) {
|
||||
} else if (rel != null && rel.groupby != null) {
|
||||
node = home.getGroupbySubnode(kstr, false);
|
||||
|
||||
if ((node == null) &&
|
||||
((rel.otherType == null) || !rel.otherType.isRelational())) {
|
||||
if (node == null && (dbm == null || !dbm.isRelational())) {
|
||||
node = (Node) db.getNode(txn, kstr);
|
||||
node.nmgr = safe;
|
||||
}
|
||||
|
||||
return node;
|
||||
} else if ((rel == null) || (rel.otherType == null) ||
|
||||
!rel.otherType.isRelational()) {
|
||||
} else if (rel == null || dbm == null || !dbm.isRelational()) {
|
||||
node = (Node) db.getNode(txn, kstr);
|
||||
node.nmgr = safe;
|
||||
node.setDbMapping(rel.otherType);
|
||||
node.setDbMapping(dbm);
|
||||
|
||||
return node;
|
||||
} else {
|
||||
DbMapping dbm = rel.otherType;
|
||||
Statement stmt = null;
|
||||
String query = null;
|
||||
long logTimeStart = logSql ? System.currentTimeMillis() : 0;
|
||||
|
@ -1629,6 +1627,7 @@ public final class NodeManager {
|
|||
} else {
|
||||
b.append(rel.buildQuery(home,
|
||||
home.getNonVirtualParent(),
|
||||
dbm,
|
||||
kstr,
|
||||
" WHERE ",
|
||||
false));
|
||||
|
@ -1644,7 +1643,7 @@ public final class NodeManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
node = createNode(rel.otherType, rs, columns, 0);
|
||||
node = createNode(dbm, rs, columns, 0);
|
||||
|
||||
fetchJoinedNodes(rs, joins, columns.length);
|
||||
|
||||
|
@ -1855,8 +1854,7 @@ public final class NodeManager {
|
|||
DbColumn[] columns2 = dbmap.getColumns();
|
||||
for (int i=0; i<columns2.length; i++) {
|
||||
Relation rel = columns2[i].getRelation();
|
||||
if (rel != null && (rel.reftype == Relation.PRIMITIVE ||
|
||||
rel.reftype == Relation.REFERENCE)) {
|
||||
if (rel != null && rel.isPrimitiveOrReference()) {
|
||||
Property prop = (Property) propBuffer.get(columns2[i].getName());
|
||||
|
||||
if (prop == null) {
|
||||
|
@ -1866,7 +1864,7 @@ public final class NodeManager {
|
|||
prop.setName(rel.propName);
|
||||
|
||||
// if the property is a pointer to another node, change the property type to NODE
|
||||
if ((rel.reftype == Relation.REFERENCE) && rel.usesPrimaryKey()) {
|
||||
if (rel.isReference() && rel.usesPrimaryKey()) {
|
||||
// FIXME: References to anything other than the primary key are not supported
|
||||
prop.convertToNodeReference(rel.otherType);
|
||||
}
|
||||
|
|
|
@ -223,15 +223,16 @@ public final class Relation {
|
|||
} else {
|
||||
boolean rprim = false;
|
||||
for (int i=0; i<constraints.length; i++) {
|
||||
if (constraints[0].foreignKeyIsPrimary()) {
|
||||
if (constraints[i].foreignKeyIsPrimary()) {
|
||||
rprim = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
referencesPrimaryKey = rprim;
|
||||
}
|
||||
|
||||
// check if this is a non-trivial reference
|
||||
if (constraints.length > 0 && !usesPrimaryKey()) {
|
||||
if (constraints.length > 1 || !usesPrimaryKey()) {
|
||||
reftype = COMPLEX_REFERENCE;
|
||||
} else {
|
||||
reftype = REFERENCE;
|
||||
|
@ -457,6 +458,14 @@ public final class Relation {
|
|||
return reftype == REFERENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this Relation describes either a primitive value
|
||||
* or an object reference.
|
||||
*/
|
||||
public boolean isPrimitiveOrReference() {
|
||||
return reftype == PRIMITIVE || reftype == REFERENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this Relation describes a collection.
|
||||
* <b>NOTE:</b> this will return true both for collection objects
|
||||
|
@ -742,6 +751,18 @@ public final class Relation {
|
|||
return virtualMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the db mapping for a propery relation.
|
||||
* @return the target mapping of this property relation
|
||||
*/
|
||||
public DbMapping getPropertyMapping() {
|
||||
// if this is an untyped virtual node, it doesn't have a dbmapping
|
||||
if (!virtual || prototype != null) {
|
||||
return otherType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Relation that defines the subnodes of a virtual node.
|
||||
*/
|
||||
|
@ -812,8 +833,18 @@ public final class Relation {
|
|||
* Build the second half of an SQL select statement according to this relation
|
||||
* and a local object.
|
||||
*/
|
||||
public String buildQuery(INode home, INode nonvirtual, String kstr, String pre,
|
||||
boolean useOrder)
|
||||
public String buildQuery(INode home, INode nonvirtual,
|
||||
String kstr, String pre, boolean useOrder)
|
||||
throws SQLException, ClassNotFoundException {
|
||||
return buildQuery(home, nonvirtual, otherType, kstr, pre, useOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the second half of an SQL select statement according to this relation
|
||||
* and a local object.
|
||||
*/
|
||||
public String buildQuery(INode home, INode nonvirtual, DbMapping otherDbm,
|
||||
String kstr, String pre, boolean useOrder)
|
||||
throws SQLException, ClassNotFoundException {
|
||||
StringBuffer q = new StringBuffer();
|
||||
String prefix = pre;
|
||||
|
@ -822,14 +853,14 @@ public final class Relation {
|
|||
q.append(prefix);
|
||||
|
||||
String accessColumn = (accessName == null) ?
|
||||
otherType.getIDField() : accessName;
|
||||
otherType.appendCondition(q, accessColumn, kstr);
|
||||
otherDbm.getIDField() : accessName;
|
||||
otherDbm.appendCondition(q, accessColumn, kstr);
|
||||
|
||||
prefix = " AND ";
|
||||
}
|
||||
|
||||
// render the constraints and filter
|
||||
renderConstraints(q, home, nonvirtual, prefix);
|
||||
renderConstraints(q, home, nonvirtual, otherDbm, prefix);
|
||||
|
||||
// add joined fetch constraints
|
||||
ownType.addJoinConstraints(q, prefix);
|
||||
|
@ -906,17 +937,36 @@ public final class Relation {
|
|||
}
|
||||
|
||||
/**
|
||||
* Render contraints and filter conditions to an SQL query string buffer
|
||||
* Render contraints and filter conditions to an SQL query string buffer.
|
||||
*
|
||||
* @param q the query string
|
||||
* @param home our home node
|
||||
* @param nonvirtual our non-virtual home node
|
||||
* @param prefix the prefix to use to append to the existing query (e.g. " AND ")
|
||||
*
|
||||
* @throws SQLException ...
|
||||
* @throws SQLException sql related exception
|
||||
* @throws ClassNotFoundException driver class not found
|
||||
*/
|
||||
public void renderConstraints(StringBuffer q, INode home,
|
||||
INode nonvirtual, String prefix)
|
||||
public void renderConstraints(StringBuffer q, INode home, INode nonvirtual,
|
||||
String prefix)
|
||||
throws SQLException, ClassNotFoundException {
|
||||
renderConstraints(q, home, nonvirtual, otherType, prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render contraints and filter conditions to an SQL query string buffer.
|
||||
*
|
||||
* @param q the query string
|
||||
* @param home our home node
|
||||
* @param nonvirtual our non-virtual home nod
|
||||
* @param otherDbm the DbMapping of the remote Node
|
||||
* @param prefix the prefix to use to append to the existing query (e.g. " AND ")
|
||||
*
|
||||
* @throws SQLException sql related exception
|
||||
* @throws ClassNotFoundException driver class not found
|
||||
*/
|
||||
public void renderConstraints(StringBuffer q, INode home, INode nonvirtual,
|
||||
DbMapping otherDbm, String prefix)
|
||||
throws SQLException, ClassNotFoundException {
|
||||
|
||||
if (constraints.length > 1 && logicalOperator != AND) {
|
||||
|
@ -926,8 +976,13 @@ public final class Relation {
|
|||
}
|
||||
|
||||
for (int i = 0; i < constraints.length; i++) {
|
||||
if (constraints[i].foreignKeyIsPrototype()) {
|
||||
// if foreign key is $prototype we already have this constraint
|
||||
// covered by doing the select on the proper table
|
||||
continue;
|
||||
}
|
||||
q.append(prefix);
|
||||
constraints[i].addToQuery(q, home, nonvirtual);
|
||||
constraints[i].addToQuery(q, home, nonvirtual, otherDbm);
|
||||
prefix = logicalOperator;
|
||||
}
|
||||
|
||||
|
@ -935,20 +990,20 @@ public final class Relation {
|
|||
q.append(")");
|
||||
prefix = " AND ";
|
||||
}
|
||||
|
||||
|
||||
// also take the prototype into consideration if someone
|
||||
// specifies an extension of an prototype inside the brakets of
|
||||
// a type.properties's collection, only nodes having this proto
|
||||
// sould appear inside the collection
|
||||
if (otherType.inheritsStorage()) {
|
||||
String protoField = otherType.getPrototypeField();
|
||||
String[] extensions = otherType.getExtensions();
|
||||
if (otherDbm.inheritsStorage()) {
|
||||
String protoField = otherDbm.getPrototypeField();
|
||||
String[] extensions = otherDbm.getExtensions();
|
||||
|
||||
// extensions should never be null for extension- and
|
||||
// extended prototypes. nevertheless we check it here
|
||||
if (extensions != null && protoField != null) {
|
||||
q.append(prefix);
|
||||
otherType.appendCondition(q, protoField, extensions);
|
||||
otherDbm.appendCondition(q, protoField, extensions);
|
||||
prefix = " AND ";
|
||||
}
|
||||
}
|
||||
|
@ -969,12 +1024,12 @@ public final class Relation {
|
|||
for (int i = 0; i < constraints.length; i++) {
|
||||
select.append(ownType.getTableName());
|
||||
select.append(".");
|
||||
select.append(constraints[i].localName);
|
||||
select.append(constraints[i].localKey);
|
||||
select.append(" = ");
|
||||
select.append(JOIN_PREFIX);
|
||||
select.append(propName);
|
||||
select.append(".");
|
||||
select.append(constraints[i].foreignName);
|
||||
select.append(constraints[i].foreignKey);
|
||||
if (isOracle) {
|
||||
// create old oracle style join - see
|
||||
// http://www.praetoriate.com/oracle_tips_outer_joins.htm
|
||||
|
@ -1034,21 +1089,28 @@ public final class Relation {
|
|||
int satisfied = 0;
|
||||
|
||||
INode nonvirtual = parent.getNonVirtualParent();
|
||||
DbMapping otherDbm = child.getDbMapping();
|
||||
if (otherDbm == null) {
|
||||
otherDbm = otherType;
|
||||
}
|
||||
|
||||
for (int i = 0; i < constraints.length; i++) {
|
||||
String propname = constraints[i].foreignProperty();
|
||||
Constraint cnst = constraints[i];
|
||||
String propname = cnst.foreignProperty(otherDbm);
|
||||
|
||||
if (propname != null) {
|
||||
INode home = constraints[i].isGroupby ? parent
|
||||
: nonvirtual;
|
||||
INode home = cnst.isGroupby ? parent
|
||||
: nonvirtual;
|
||||
String value = null;
|
||||
|
||||
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
|
||||
if (cnst.localKeyIsPrimary(home.getDbMapping())) {
|
||||
value = home.getID();
|
||||
} else if (cnst.localKeyIsPrototype()) {
|
||||
value = home.getDbMapping().getStorageTypeName();
|
||||
} else if (ownType.isRelational()) {
|
||||
value = home.getString(constraints[i].localProperty());
|
||||
value = home.getString(cnst.localProperty());
|
||||
} else {
|
||||
value = home.getString(constraints[i].localName);
|
||||
value = home.getString(cnst.localKey);
|
||||
}
|
||||
|
||||
count++;
|
||||
|
@ -1084,32 +1146,40 @@ public final class Relation {
|
|||
Node home = parent.getNonVirtualParent();
|
||||
|
||||
for (int i = 0; i < constraints.length; i++) {
|
||||
Constraint cnst = constraints[i];
|
||||
// don't set groupby constraints since we don't know if the
|
||||
// parent node is the base node or a group node
|
||||
if (constraints[i].isGroupby) {
|
||||
if (cnst.isGroupby) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if we update the local or the other object, depending on
|
||||
// whether the primary key of either side is used.
|
||||
|
||||
if (constraints[i].foreignKeyIsPrimary()) {
|
||||
String localProp = constraints[i].localProperty();
|
||||
boolean foreignIsPrimary = cnst.foreignKeyIsPrimary();
|
||||
if (foreignIsPrimary || cnst.foreignKeyIsPrototype()) {
|
||||
String localProp = cnst.localProperty();
|
||||
if (localProp == null) {
|
||||
System.err.println ("Error: column "+constraints[i].localName+
|
||||
ownType.app.logError("Error: column " + cnst.localKey +
|
||||
" must be mapped in order to be used as constraint in "+
|
||||
Relation.this);
|
||||
} else {
|
||||
home.setString(localProp, child.getID());
|
||||
String value = foreignIsPrimary ?
|
||||
child.getID() : child.getDbMapping().getStorageTypeName();
|
||||
home.setString(localProp, value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
|
||||
if (crel != null) {
|
||||
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
|
||||
DbMapping otherDbm = child.getDbMapping();
|
||||
if (otherDbm == null) {
|
||||
otherDbm = otherType;
|
||||
}
|
||||
|
||||
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
|
||||
Relation crel = otherDbm.columnNameToRelation(cnst.foreignKey);
|
||||
|
||||
if (crel != null) {
|
||||
|
||||
if (cnst.localKeyIsPrimary(home.getDbMapping())) {
|
||||
// only set node if property in child object is defined as reference.
|
||||
if (crel.reftype == REFERENCE) {
|
||||
INode currentValue = child.getNode(crel.propName);
|
||||
|
@ -1131,16 +1201,13 @@ public final class Relation {
|
|||
child.setString(crel.propName, home.getID());
|
||||
}
|
||||
} else if (crel.reftype == PRIMITIVE) {
|
||||
Property prop = null;
|
||||
|
||||
if (ownType.isRelational()) {
|
||||
prop = home.getProperty(constraints[i].localProperty());
|
||||
if (cnst.localKeyIsPrototype()) {
|
||||
child.setString(crel.propName, home.getDbMapping().getStorageTypeName());
|
||||
} else {
|
||||
prop = home.getProperty(constraints[i].localName);
|
||||
}
|
||||
|
||||
if (prop != null) {
|
||||
child.set(crel.propName, prop.getValue(), prop.getType());
|
||||
Property prop = home.getProperty(cnst.localProperty());
|
||||
if (prop != null) {
|
||||
child.set(crel.propName, prop.getValue(), prop.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1154,28 +1221,33 @@ public final class Relation {
|
|||
Node home = parent.getNonVirtualParent();
|
||||
|
||||
for (int i = 0; i < constraints.length; i++) {
|
||||
Constraint cnst = constraints[i];
|
||||
// don't set groupby constraints since we don't know if the
|
||||
// parent node is the base node or a group node
|
||||
if (constraints[i].isGroupby) {
|
||||
if (cnst.isGroupby) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if we update the local or the other object, depending on
|
||||
// whether the primary key of either side is used.
|
||||
|
||||
if (constraints[i].foreignKeyIsPrimary()) {
|
||||
String localProp = constraints[i].localProperty();
|
||||
if (cnst.foreignKeyIsPrimary() || cnst.foreignKeyIsPrototype()) {
|
||||
String localProp = cnst.localProperty();
|
||||
if (localProp != null) {
|
||||
home.setString(localProp, null);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
|
||||
if (crel != null) {
|
||||
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
|
||||
DbMapping otherDbm = child.getDbMapping();
|
||||
if (otherDbm == null) {
|
||||
otherDbm = otherType;
|
||||
}
|
||||
|
||||
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
|
||||
Relation crel = otherDbm.columnNameToRelation(cnst.foreignKey);
|
||||
|
||||
if (crel != null) {
|
||||
if (cnst.localKeyIsPrimary(home.getDbMapping())) {
|
||||
// only set node if property in child object is defined as reference.
|
||||
if (crel.reftype == REFERENCE) {
|
||||
INode currentValue = child.getNode(crel.propName);
|
||||
|
@ -1187,17 +1259,7 @@ public final class Relation {
|
|||
child.setString(crel.propName, null);
|
||||
}
|
||||
} else if (crel.reftype == PRIMITIVE) {
|
||||
Property prop = null;
|
||||
|
||||
if (ownType.isRelational()) {
|
||||
prop = home.getProperty(constraints[i].localProperty());
|
||||
} else {
|
||||
prop = home.getProperty(constraints[i].localName);
|
||||
}
|
||||
|
||||
if (prop != null) {
|
||||
child.setString(crel.propName, null);
|
||||
}
|
||||
child.setString(crel.propName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1209,10 +1271,13 @@ public final class Relation {
|
|||
public Map getKeyParts(INode home) {
|
||||
Map map = new HashMap();
|
||||
for (int i=0; i<constraints.length; i++) {
|
||||
if (ownType.getIDField().equalsIgnoreCase(constraints[i].localName)) {
|
||||
map.put(constraints[i].foreignName, home.getID());
|
||||
Constraint cnst = constraints[i];
|
||||
if (cnst.localKeyIsPrimary(ownType)) {
|
||||
map.put(cnst.foreignKey, home.getID());
|
||||
} else if (cnst.localKeyIsPrototype()) {
|
||||
map.put(cnst.foreignKey, home.getDbMapping().getStorageTypeName());
|
||||
} else {
|
||||
map.put(constraints[i].foreignName, home.getString(constraints[i].localProperty()));
|
||||
map.put(cnst.foreignKey, home.getString(cnst.localProperty()));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
|
@ -1246,53 +1311,77 @@ public final class Relation {
|
|||
* establish a relation between database mapped objects.
|
||||
*/
|
||||
class Constraint {
|
||||
String localName;
|
||||
String foreignName;
|
||||
String localKey;
|
||||
String foreignKey;
|
||||
boolean isGroupby;
|
||||
|
||||
Constraint(String local, String foreign, boolean groupby) {
|
||||
localName = local;
|
||||
foreignName = foreign;
|
||||
localKey = local;
|
||||
foreignKey = foreign;
|
||||
isGroupby = groupby;
|
||||
}
|
||||
|
||||
public void addToQuery(StringBuffer q, INode home, INode nonvirtual)
|
||||
public void addToQuery(StringBuffer q, INode home, INode nonvirtual, DbMapping otherDbm)
|
||||
throws SQLException, ClassNotFoundException {
|
||||
String local = null;
|
||||
String local;
|
||||
INode ref = isGroupby ? home : nonvirtual;
|
||||
|
||||
if ((localName == null) ||
|
||||
localName.equalsIgnoreCase(ref.getDbMapping().getIDField())) {
|
||||
if (localKeyIsPrimary(ref.getDbMapping())) {
|
||||
local = ref.getID();
|
||||
} else if (localKeyIsPrototype()) {
|
||||
local = ref.getDbMapping().getStorageTypeName();
|
||||
} else {
|
||||
String homeprop = ownType.columnNameToProperty(localName);
|
||||
|
||||
String homeprop = ownType.columnNameToProperty(localKey);
|
||||
if (homeprop == null) {
|
||||
throw new SQLException("Invalid local name '" + localKey +
|
||||
"' on " + ownType);
|
||||
}
|
||||
local = ref.getString(homeprop);
|
||||
}
|
||||
|
||||
otherType.appendCondition(q, foreignName, local);
|
||||
String columnName;
|
||||
if (foreignKeyIsPrimary()) {
|
||||
columnName = otherDbm.getIDField();
|
||||
} else {
|
||||
columnName = foreignKey;
|
||||
}
|
||||
otherDbm.appendCondition(q, columnName, local);
|
||||
}
|
||||
|
||||
public boolean foreignKeyIsPrimary() {
|
||||
return (foreignName == null) ||
|
||||
foreignName.equalsIgnoreCase(otherType.getIDField());
|
||||
return (foreignKey == null) ||
|
||||
"$id".equalsIgnoreCase(foreignKey) ||
|
||||
foreignKey.equalsIgnoreCase(otherType.getIDField());
|
||||
}
|
||||
|
||||
public boolean foreignKeyIsPrototype() {
|
||||
return "$prototype".equalsIgnoreCase(foreignKey);
|
||||
}
|
||||
|
||||
public boolean localKeyIsPrimary(DbMapping homeMapping) {
|
||||
return (homeMapping == null) || (localName == null) ||
|
||||
localName.equalsIgnoreCase(homeMapping.getIDField());
|
||||
return (homeMapping == null) || (localKey == null) ||
|
||||
"$id".equalsIgnoreCase(localKey) ||
|
||||
localKey.equalsIgnoreCase(homeMapping.getIDField());
|
||||
}
|
||||
|
||||
public String foreignProperty() {
|
||||
return otherType.columnNameToProperty(foreignName);
|
||||
public boolean localKeyIsPrototype() {
|
||||
return "$prototype".equalsIgnoreCase(localKey);
|
||||
}
|
||||
|
||||
public String foreignProperty(DbMapping otherDbm) {
|
||||
if (otherDbm.isRelational())
|
||||
return otherDbm.columnNameToProperty(foreignKey);
|
||||
return foreignKey;
|
||||
}
|
||||
|
||||
public String localProperty() {
|
||||
return ownType.columnNameToProperty(localName);
|
||||
if (ownType.isRelational())
|
||||
return ownType.columnNameToProperty(localKey);
|
||||
return localKey;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return localName + "=" + otherType.getTypeName() + "." + foreignName;
|
||||
return localKey + "=" + otherType.getTypeName() + "." + foreignKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
|||
* @return the default value for the object
|
||||
*/
|
||||
public Object getDefaultValue(Class hint) {
|
||||
return toString();
|
||||
return node == null ? toString() : node.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -981,12 +981,15 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of this object
|
||||
*
|
||||
* @return ...
|
||||
* Return a string representation of this HopObject.
|
||||
* @return a string representing this HopObject
|
||||
*/
|
||||
public String toString() {
|
||||
return (className != null) ? ("[HopObject " + className + "]") : "[HopObject]";
|
||||
if (node == null) {
|
||||
return "[HopObject prototype " + className + "]";
|
||||
} else {
|
||||
return "[HopObject " + node.getName() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue