* Merging updateable_collections branch (with a few changes along the way)
This commit is contained in:
parent
d4ac3d2726
commit
852543386c
13 changed files with 1607 additions and 115 deletions
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A subclass of Vector that implements the Externalizable interface in order
|
||||
* to be able to control how it is serialized and deserialized.
|
||||
*/
|
||||
public class ExternalizableVector extends ArrayList implements Externalizable {
|
||||
static final long serialVersionUID = 2316243615310540423L;
|
||||
|
||||
/**
|
||||
* Reads an externalized representation of the list from a stream.
|
||||
*
|
||||
* @param in the input stream to read from
|
||||
*
|
||||
* @throws IOException ...
|
||||
*/
|
||||
public synchronized void readExternal(ObjectInput in)
|
||||
throws IOException {
|
||||
try {
|
||||
int size = in.readInt();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
add(in.readObject());
|
||||
} catch (ClassNotFoundException x) {
|
||||
throw new IOException(x.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an externalized representation of the list to the stream.
|
||||
*
|
||||
* @param out the output stream to write to
|
||||
*
|
||||
* @throws IOException ...
|
||||
*/
|
||||
public synchronized void writeExternal(ObjectOutput out)
|
||||
throws IOException {
|
||||
int size = size();
|
||||
|
||||
out.writeInt(size);
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
out.writeObject(get(i));
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ public final class Node implements INode, Serializable {
|
|||
protected NodeHandle parentHandle;
|
||||
|
||||
// Ordered list of subnodes of this node
|
||||
private List subnodes;
|
||||
private SubnodeList subnodes;
|
||||
|
||||
// Named subnodes (properties) of this node
|
||||
private Hashtable propMap;
|
||||
|
@ -201,7 +201,7 @@ public final class Node implements INode, Serializable {
|
|||
created = in.readLong();
|
||||
lastmodified = in.readLong();
|
||||
|
||||
subnodes = (ExternalizableVector) in.readObject();
|
||||
subnodes = (SubnodeList) in.readObject();
|
||||
// left-over from links vector
|
||||
in.readObject();
|
||||
propMap = (Hashtable) in.readObject();
|
||||
|
@ -250,7 +250,7 @@ public final class Node implements INode, Serializable {
|
|||
/**
|
||||
* used by Xml deserialization
|
||||
*/
|
||||
public synchronized void setSubnodes(List subnodes) {
|
||||
public synchronized void setSubnodes(SubnodeList subnodes) {
|
||||
this.subnodes = subnodes;
|
||||
}
|
||||
|
||||
|
@ -586,7 +586,7 @@ public final class Node implements INode, Serializable {
|
|||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param nmgr
|
||||
*/
|
||||
public void setWrappedNodeManager(WrappedNodeManager nmgr) {
|
||||
|
@ -845,12 +845,13 @@ public final class Node implements INode, Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add a node to this Node's subnodes, making the added node persistent if it
|
||||
* hasn't been before and this Node is already persistent.
|
||||
*
|
||||
* @param elem the node to add to this Nodes subnode-list
|
||||
* @param where the index-position where this node has to be added
|
||||
*
|
||||
* @param elem ...
|
||||
* @param where ...
|
||||
*
|
||||
* @return ...
|
||||
* @return the added node itselve
|
||||
*/
|
||||
public INode addNode(INode elem, int where) {
|
||||
Node node = null;
|
||||
|
@ -932,7 +933,7 @@ public final class Node implements INode, Serializable {
|
|||
} else {
|
||||
// create subnode list if necessary
|
||||
if (subnodes == null) {
|
||||
subnodes = new ExternalizableVector();
|
||||
subnodes = createSubnodeList();
|
||||
}
|
||||
|
||||
// check if subnode accessname is set. If so, check if another node
|
||||
|
@ -1097,7 +1098,7 @@ public final class Node implements INode, Serializable {
|
|||
if (state != TRANSIENT && rel.otherType != null && rel.otherType.isRelational()) {
|
||||
return nmgr.getNode(this, name, rel);
|
||||
} else {
|
||||
// Do what we have to do: loop through subnodes and
|
||||
// Do what we have to do: loop through subnodes and
|
||||
// check if any one matches
|
||||
String propname = rel.groupby != null ? "groupname" : rel.accessName;
|
||||
INode node = null;
|
||||
|
@ -1243,7 +1244,7 @@ public final class Node implements INode, Serializable {
|
|||
loadNodes();
|
||||
|
||||
if (subnodes == null) {
|
||||
subnodes = new ExternalizableVector();
|
||||
subnodes = new SubnodeList();
|
||||
}
|
||||
|
||||
if (create || subnodes.contains(new NodeHandle(new SyntheticKey(getKey(), sid)))) {
|
||||
|
@ -1537,7 +1538,7 @@ public final class Node implements INode, Serializable {
|
|||
* Depending on the subnode.loadmode specified in the type.properties, we'll load just the
|
||||
* ID index or the actual nodes.
|
||||
*/
|
||||
protected void loadNodes() {
|
||||
public void loadNodes() {
|
||||
// Don't do this for transient nodes which don't have an explicit subnode relation set
|
||||
if (((state == TRANSIENT) || (state == NEW)) && (subnodeRelation == null)) {
|
||||
return;
|
||||
|
@ -1556,11 +1557,15 @@ public final class Node implements INode, Serializable {
|
|||
// also reload if the type mapping has changed.
|
||||
lastChange = Math.max(lastChange, dbmap.getLastTypeChange());
|
||||
|
||||
if ((lastChange >= lastSubnodeFetch) || (subnodes == null)) {
|
||||
if (subRel.aggressiveLoading) {
|
||||
subnodes = nmgr.getNodes(this, dbmap.getSubnodeRelation());
|
||||
if ((lastChange >= lastSubnodeFetch && !subRel.autoSorted) ||
|
||||
(subnodes == null)) {
|
||||
if (subRel.updateCriteria!=null) {
|
||||
// updateSubnodeList is setting the subnodes directly returning an integer
|
||||
nmgr.updateSubnodeList(this, subRel);
|
||||
} else if (subRel.aggressiveLoading) {
|
||||
subnodes = nmgr.getNodes(this, subRel);
|
||||
} else {
|
||||
subnodes = nmgr.getNodeIDs(this, dbmap.getSubnodeRelation());
|
||||
subnodes = nmgr.getNodeIDs(this, subRel);
|
||||
}
|
||||
|
||||
lastSubnodeFetch = System.currentTimeMillis();
|
||||
|
@ -1569,6 +1574,23 @@ public final class Node implements INode, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an empty subnodelist. This empty List is an instance of the Class
|
||||
* used for this Nodes subnode-list
|
||||
* @return List an empty List of the type used by this Node
|
||||
*/
|
||||
public SubnodeList createSubnodeList() {
|
||||
Relation rel = this.dbmap == null ? null : this.dbmap.getSubnodeRelation();
|
||||
if (rel != null && rel.updateCriteria != null) {
|
||||
subnodes = new UpdateableSubnodeList(rel);
|
||||
} else if (rel != null && rel.autoSorted) {
|
||||
subnodes = new OrderedSubnodeList(rel);
|
||||
} else {
|
||||
subnodes = new SubnodeList();
|
||||
}
|
||||
return subnodes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
|
@ -1609,6 +1631,10 @@ public final class Node implements INode, Serializable {
|
|||
keys[i] = ((NodeHandle) subnodes.get(i + startIndex)).getKey();
|
||||
}
|
||||
|
||||
prefetchChildren (keys);
|
||||
}
|
||||
|
||||
public void prefetchChildren (Key[] keys) throws Exception {
|
||||
nmgr.nmgr.prefetchNodes(this, dbmap.getSubnodeRelation(), keys);
|
||||
}
|
||||
|
||||
|
@ -2412,7 +2438,7 @@ public final class Node implements INode, Serializable {
|
|||
|
||||
if (relational) {
|
||||
p.setStringValue(null);
|
||||
notifyPropertyChange(propname);
|
||||
notifyPropertyChange(propname);
|
||||
}
|
||||
|
||||
lastmodified = System.currentTimeMillis();
|
||||
|
@ -2627,4 +2653,28 @@ public final class Node implements INode, Serializable {
|
|||
System.err.println("properties: " + propMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method get's called from the JavaScript environment
|
||||
* (HopObject.updateSubnodes() or HopObject.collection.updateSubnodes()))
|
||||
* The subnode-collection will be updated with a selectstatement getting all
|
||||
* Nodes having a higher id than the highest id currently contained within
|
||||
* this Node's subnoderelation. If this subnodelist has a special order
|
||||
* all nodes will be loaded honoring this order.
|
||||
* Example:
|
||||
* order by somefield1 asc, somefieled2 desc
|
||||
* gives a where-clausel like the following:
|
||||
* (somefiled1 > theHighestKnownValue value and somefield2 < theLowestKnownValue)
|
||||
* @return the number of loaded nodes within this collection update
|
||||
*/
|
||||
public int updateSubnodes () {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -226,7 +226,7 @@ public final class NodeManager {
|
|||
|
||||
Transactor tx = (Transactor) Thread.currentThread();
|
||||
|
||||
Key key = null;
|
||||
Key key;
|
||||
|
||||
// check what kind of object we're looking for and make an apropriate key
|
||||
if (rel.isComplexReference()) {
|
||||
|
@ -864,7 +864,7 @@ public final class NodeManager {
|
|||
* Loades subnodes via subnode relation. Only the ID index is loaded, the nodes are
|
||||
* loaded later on demand.
|
||||
*/
|
||||
public List getNodeIDs(Node home, Relation rel) throws Exception {
|
||||
public SubnodeList getNodeIDs(Node home, Relation rel) throws Exception {
|
||||
// Transactor tx = (Transactor) Thread.currentThread ();
|
||||
// tx.timer.beginEvent ("getNodeIDs "+home);
|
||||
|
||||
|
@ -873,7 +873,7 @@ public final class NodeManager {
|
|||
throw new RuntimeException("NodeMgr.getNodeIDs called for non-relational node " +
|
||||
home);
|
||||
} else {
|
||||
List retval = new ArrayList();
|
||||
SubnodeList retval = home.createSubnodeList();
|
||||
|
||||
// if we do a groupby query (creating an intermediate layer of groupby nodes),
|
||||
// retrieve the value of that field instead of the primary key
|
||||
|
@ -890,7 +890,6 @@ public final class NodeManager {
|
|||
String query = null;
|
||||
|
||||
try {
|
||||
|
||||
StringBuffer b = new StringBuffer("SELECT ");
|
||||
|
||||
if (rel.queryHints != null) {
|
||||
|
@ -941,7 +940,7 @@ public final class NodeManager {
|
|||
? (Key) new DbKey(rel.otherType, kstr)
|
||||
: (Key) new SyntheticKey(k, kstr);
|
||||
|
||||
retval.add(new NodeHandle(key));
|
||||
retval.addSorted(new NodeHandle(key));
|
||||
|
||||
// if these are groupby nodes, evict nullNode keys
|
||||
if (rel.groupby != null) {
|
||||
|
@ -975,7 +974,7 @@ public final class NodeManager {
|
|||
* actually loades all nodes in one go, which is better for small node collections.
|
||||
* This method is used when xxx.loadmode=aggressive is specified.
|
||||
*/
|
||||
public List getNodes(Node home, Relation rel) throws Exception {
|
||||
public SubnodeList getNodes(Node home, Relation rel) throws Exception {
|
||||
// This does not apply for groupby nodes - use getNodeIDs instead
|
||||
if (rel.groupby != null) {
|
||||
return getNodeIDs(home, rel);
|
||||
|
@ -988,7 +987,7 @@ public final class NodeManager {
|
|||
throw new RuntimeException("NodeMgr.getNodes called for non-relational node " +
|
||||
home);
|
||||
} else {
|
||||
List retval = new ArrayList();
|
||||
SubnodeList retval = home.createSubnodeList();
|
||||
DbMapping dbm = rel.otherType;
|
||||
|
||||
Connection con = dbm.getConnection();
|
||||
|
@ -1031,7 +1030,7 @@ public final class NodeManager {
|
|||
}
|
||||
Key primKey = node.getKey();
|
||||
|
||||
retval.add(new NodeHandle(primKey));
|
||||
retval.addSorted(new NodeHandle(primKey));
|
||||
|
||||
// do we need to synchronize on primKey here?
|
||||
synchronized (cache) {
|
||||
|
@ -1062,6 +1061,159 @@ public final class NodeManager {
|
|||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a UpdateableSubnodeList retrieving all values having
|
||||
* higher Values according to the updateCriteria's set for this Collection's Relation
|
||||
* The returned Map-Object has two Properties:
|
||||
* addedNodes = an Integer representing the number of Nodes added to this collection
|
||||
* newNodes = an Integer representing the number of Records returned by the Select-Statement
|
||||
* These two values may be different if a max-size is defined for this Collection and a new
|
||||
* node would be outside of this Border because of the ordering of this collection.
|
||||
* @param home the home of this subnode-list
|
||||
* @param rel the relation the home-node has to the nodes contained inside the subnodelist
|
||||
* @return A map having two properties of type String (newNodes (number of nodes retreived by the select-statment), addedNodes (nodes added to the collection))
|
||||
* @throws Exception
|
||||
*/
|
||||
public int updateSubnodeList(Node home, Relation rel) throws Exception {
|
||||
if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) {
|
||||
// this should never be called for embedded nodes
|
||||
throw new RuntimeException("NodeMgr.updateSubnodeList called for non-relational node " +
|
||||
home);
|
||||
} else {
|
||||
List list = home.getSubnodeList();
|
||||
if (list == null)
|
||||
list = home.createSubnodeList();
|
||||
|
||||
if (!(list instanceof UpdateableSubnodeList))
|
||||
throw new RuntimeException ("unable to update SubnodeList not marked as updateable (" + rel.propName + ")");
|
||||
|
||||
UpdateableSubnodeList sublist = (UpdateableSubnodeList) list;
|
||||
|
||||
// FIXME: grouped subnodes aren't supported yet
|
||||
if (rel.groupby != null)
|
||||
throw new RuntimeException ("update not yet supported on grouped collections");
|
||||
|
||||
String idfield = rel.otherType.getIDField();
|
||||
Connection con = rel.otherType.getConnection();
|
||||
String table = rel.otherType.getTableName();
|
||||
|
||||
Statement stmt = null;
|
||||
|
||||
try {
|
||||
String q = null;
|
||||
|
||||
StringBuffer b = new StringBuffer();
|
||||
if (rel.loadAggressively()) {
|
||||
b.append (rel.otherType.getSelect(rel));
|
||||
} else {
|
||||
b.append ("SELECT ");
|
||||
if (rel.queryHints != null) {
|
||||
b.append(rel.queryHints).append(" ");
|
||||
}
|
||||
b.append(table).append('.')
|
||||
.append(idfield).append(" FROM ")
|
||||
.append(table);
|
||||
|
||||
rel.appendAdditionalTables(b);
|
||||
}
|
||||
String updateCriteria = sublist.getUpdateCriteria();
|
||||
if (home.getSubnodeRelation() != null) {
|
||||
if (updateCriteria != null) {
|
||||
b.append (" WHERE ");
|
||||
b.append (sublist.getUpdateCriteria());
|
||||
b.append (" AND ");
|
||||
b.append (home.getSubnodeRelation());
|
||||
} else {
|
||||
b.append (" WHERE ");
|
||||
b.append (home.getSubnodeRelation());
|
||||
}
|
||||
} else {
|
||||
if (updateCriteria != null) {
|
||||
b.append (" WHERE ");
|
||||
b.append (updateCriteria);
|
||||
b.append (rel.buildQuery(home,
|
||||
home.getNonVirtualParent(),
|
||||
null,
|
||||
" AND ",
|
||||
true));
|
||||
} else {
|
||||
b.append (rel.buildQuery(home,
|
||||
home.getNonVirtualParent(),
|
||||
null,
|
||||
" WHERE ",
|
||||
true));
|
||||
}
|
||||
q = b.toString();
|
||||
}
|
||||
|
||||
long logTimeStart = logSql ? System.currentTimeMillis() : 0;
|
||||
|
||||
stmt = con.createStatement();
|
||||
|
||||
if (rel.maxSize > 0) {
|
||||
stmt.setMaxRows(rel.maxSize);
|
||||
}
|
||||
|
||||
ResultSet result = stmt.executeQuery(q);
|
||||
|
||||
if (logSql) {
|
||||
long logTimeStop = System.currentTimeMillis();
|
||||
logSqlStatement("SQL SELECT_UPDATE_SUBNODE_LIST", table,
|
||||
logTimeStart, logTimeStop, q);
|
||||
}
|
||||
|
||||
// problem: how do we derive a SyntheticKey from a not-yet-persistent Node?
|
||||
// Key k = (rel.groupby != null) ? home.getKey() : null;
|
||||
// int cntr = 0;
|
||||
|
||||
DbColumn[] columns = rel.loadAggressively() ? rel.otherType.getColumns() : null;
|
||||
List newNodes = new ArrayList(rel.maxSize);
|
||||
while (result.next()) {
|
||||
String kstr = result.getString(1);
|
||||
|
||||
// jump over null values - this can happen especially when the selected
|
||||
// column is a group-by column.
|
||||
if (kstr == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// make the proper key for the object, either a generic DB key or a groupby key
|
||||
Key key;
|
||||
if (rel.loadAggressively()) {
|
||||
Node node = createNode(rel.otherType, result, columns, 0);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
key = node.getKey();
|
||||
} else {
|
||||
key = new DbKey(rel.otherType, kstr);
|
||||
}
|
||||
newNodes.add(new NodeHandle(key));
|
||||
|
||||
// if these are groupby nodes, evict nullNode keys
|
||||
if (rel.groupby != null) {
|
||||
Node n = (Node) cache.get(key);
|
||||
|
||||
if ((n != null) && n.isNullNode()) {
|
||||
evictKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// System.err.println("GOT NEW NODES: " + newNodes);
|
||||
if (!newNodes.isEmpty())
|
||||
sublist.addAll(newNodes);
|
||||
return newNodes.size();
|
||||
} finally {
|
||||
if (stmt != null) {
|
||||
try {
|
||||
stmt.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -1165,14 +1317,14 @@ public final class NodeManager {
|
|||
if (groupbyProp != null) {
|
||||
groupName = node.getString(groupbyProp);
|
||||
|
||||
List sn = (List) groupbySubnodes.get(groupName);
|
||||
SubnodeList sn = (SubnodeList) groupbySubnodes.get(groupName);
|
||||
|
||||
if (sn == null) {
|
||||
sn = new ExternalizableVector();
|
||||
sn = new SubnodeList();
|
||||
groupbySubnodes.put(groupName, sn);
|
||||
}
|
||||
|
||||
sn.add(new NodeHandle(primKey));
|
||||
sn.addSorted(new NodeHandle(primKey));
|
||||
}
|
||||
|
||||
// if relation doesn't use primary key as accessName, get accessName value
|
||||
|
@ -1220,7 +1372,7 @@ public final class NodeManager {
|
|||
|
||||
Node groupnode = home.getGroupbySubnode(groupname, true);
|
||||
|
||||
groupnode.setSubnodes((List) groupbySubnodes.get(groupname));
|
||||
groupnode.setSubnodes((SubnodeList) groupbySubnodes.get(groupname));
|
||||
groupnode.lastSubnodeFetch = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
@ -1696,7 +1848,7 @@ public final class NodeManager {
|
|||
Reader in = rs.getCharacterStream(i+1+offset);
|
||||
char[] buffer = new char[2048];
|
||||
int read = 0;
|
||||
int r = 0;
|
||||
int r;
|
||||
|
||||
while ((r = in.read(buffer, read, buffer.length - read)) > -1) {
|
||||
read += r;
|
||||
|
|
471
src/helma/objectmodel/db/OrderedSubnodeList.java
Normal file
471
src/helma/objectmodel/db/OrderedSubnodeList.java
Normal file
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author manfred andres
|
||||
* This subnode-collection may be used to add nodes in an ordered way depending on
|
||||
* the given order. It is also possible to retrieve views of this list in a different
|
||||
* order. These views will be cached and automatically updated if this List's add-
|
||||
* or remove-methods are called.
|
||||
*/
|
||||
public class OrderedSubnodeList extends SubnodeList {
|
||||
HashMap views = null;
|
||||
private final OrderedSubnodeList origin;
|
||||
|
||||
// an array containing the order-fields
|
||||
private final String orderProperties[];
|
||||
// an array containing the direction for ordering
|
||||
private final boolean orderIsDesc[];
|
||||
|
||||
// the relation which is the basis for this collection
|
||||
final Relation rel;
|
||||
|
||||
/**
|
||||
* Construct a new OrderedSubnodeList. The Relation is needed
|
||||
* to get the information about the ORDERING
|
||||
*/
|
||||
public OrderedSubnodeList (Relation rel) {
|
||||
this.rel = rel;
|
||||
this.origin = null;
|
||||
// check the order of this collection for automatically sorting
|
||||
// in the values in the correct order
|
||||
if (rel.order == null) {
|
||||
orderProperties=null;
|
||||
orderIsDesc=null;
|
||||
} else {
|
||||
String singleOrders[] = rel.order.split(",");
|
||||
orderProperties = new String[singleOrders.length];
|
||||
orderIsDesc = new boolean[singleOrders.length];
|
||||
DbMapping dbm = rel.otherType;
|
||||
for (int i = 0; i < singleOrders.length; i++) {
|
||||
String currOrder[] = singleOrders[i].trim().split(" ");
|
||||
if (currOrder[0].equalsIgnoreCase(rel.otherType.getIDField())) {
|
||||
orderProperties[i]=null;
|
||||
} else {
|
||||
orderProperties[i] = dbm.columnNameToProperty(currOrder[0]);
|
||||
}
|
||||
System.err.println("ORDER PROP " + i + " IS " + orderProperties[i] + " FROM " +currOrder[0]);
|
||||
if (currOrder.length < 2
|
||||
|| "ASC".equalsIgnoreCase(currOrder[1]))
|
||||
orderIsDesc[i]=false;
|
||||
else
|
||||
orderIsDesc[i]=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor is used to create a view for the OrderedSubnodeList origin.
|
||||
* @param origin the origin-list which holds the elements
|
||||
* @param expr the new order for this view
|
||||
* @param rel the relation given for the origin-list
|
||||
*/
|
||||
public OrderedSubnodeList (OrderedSubnodeList origin, String expr, Relation rel) {
|
||||
this.origin = origin;
|
||||
this.rel = rel;
|
||||
if (expr==null) {
|
||||
orderProperties=null;
|
||||
orderIsDesc=null;
|
||||
} else {
|
||||
String singleOrders[] = expr.split(",");
|
||||
orderProperties = new String[singleOrders.length];
|
||||
orderIsDesc = new boolean[singleOrders.length];
|
||||
DbMapping dbm = rel.otherType;
|
||||
for (int i = 0; i<singleOrders.length; i++) {
|
||||
String currOrder[] = singleOrders[i].trim().split(" ");
|
||||
if (currOrder[0].equalsIgnoreCase("_id")) {
|
||||
orderProperties[i]=null;
|
||||
} else {
|
||||
if (dbm.propertyToColumnName(currOrder[0])==null)
|
||||
throw new RuntimeException ("Properties must be mapped to get an ordered collection for these properties.");
|
||||
orderProperties[i]=currOrder[0];
|
||||
}
|
||||
if (currOrder.length < 2
|
||||
|| "ASC".equalsIgnoreCase(currOrder[1]))
|
||||
orderIsDesc[i]=false;
|
||||
else
|
||||
orderIsDesc[i]=true;
|
||||
}
|
||||
}
|
||||
if (origin == null)
|
||||
return;
|
||||
this.sortIn(origin, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified object to this list performing
|
||||
* custom ordering
|
||||
*
|
||||
* @param obj element to be inserted.
|
||||
*/
|
||||
public boolean add(Object obj) {
|
||||
System.err.println("******** SORT-ADDING " + obj);
|
||||
return add(obj, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified object to this list without performing
|
||||
* custom ordering.
|
||||
*
|
||||
* @param obj element to be inserted.
|
||||
*/
|
||||
public boolean addSorted(Object obj) {
|
||||
return add(obj, true);
|
||||
}
|
||||
|
||||
private boolean add(Object obj, boolean sorted) {
|
||||
if (origin != null)
|
||||
return origin.add(obj);
|
||||
vAdd(obj);
|
||||
while (rel.maxSize>0 && this.size() >= rel.maxSize)
|
||||
super.remove(0);
|
||||
// escape sorting for presorted adds and grouped nodes
|
||||
if (sorted || rel.groupby != null) {
|
||||
super.add(obj);
|
||||
} else {
|
||||
sortIn(obj);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a new node honoring the Nodes SQL-Order
|
||||
* @param obj the object to add
|
||||
*/
|
||||
public boolean sortIn(Object obj) {
|
||||
// no order, just add
|
||||
if (this.orderProperties==null)
|
||||
return super.add(obj);
|
||||
vAdd(obj);
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
int idx = this.determineNodePosition((NodeHandle) obj, 0);
|
||||
System.err.println("Position: " + idx);
|
||||
if (idx<0)
|
||||
return super.add(obj);
|
||||
else
|
||||
super.add(idx, obj);
|
||||
return true;
|
||||
} finally {
|
||||
System.out.println("Sortmillis: " + (System.currentTimeMillis() - start));
|
||||
}
|
||||
}
|
||||
|
||||
private void vAdd (Object obj) {
|
||||
if (views==null || origin!=null || views.size()<1)
|
||||
return;
|
||||
for (Iterator i = views.values().iterator(); i.hasNext(); ) {
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) i.next();
|
||||
osl.sortIn(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addAll(Collection col) {
|
||||
return sortIn(col, true) > 0;
|
||||
}
|
||||
|
||||
private void vAddAll (Collection col) {
|
||||
if (views==null || origin!=null || views.size()<1)
|
||||
return;
|
||||
for (Iterator i = views.values().iterator(); i.hasNext(); ) {
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) i.next();
|
||||
osl.addAll(col);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(int idx, Object obj) {
|
||||
if (this.orderProperties!=null)
|
||||
throw new RuntimeException ("Indexed add isn't alowed for ordered subnodes");
|
||||
super.add(idx, obj);
|
||||
vAdd(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all nodes contained inside the specified Collection to this
|
||||
* UpdateableSubnodeList. The order of the added Nodes is asumed to
|
||||
* be ordered according to the SQL-Order-Clausel given for this
|
||||
* Subnodecollection but doesn't prevent adding of unordered Collections.
|
||||
* Ordered Collections will be sorted in more efficient than unordered ones.
|
||||
* @param col the collection containing all elements to add in the order returned by the select-statement
|
||||
* @param colHasDefaultOrder true if the given collection does have the default-order defined by the relation
|
||||
*/
|
||||
public int sortIn (Collection col, boolean colHasDefaultOrder) {
|
||||
vAddAll(col);
|
||||
int cntr=0;
|
||||
// there is no order specified, add on top
|
||||
if (orderProperties==null) {
|
||||
for (Iterator i = col.iterator(); i.hasNext(); ) {
|
||||
super.add(cntr, i.next());
|
||||
cntr++;
|
||||
}
|
||||
if (rel.maxSize > 0) {
|
||||
int diff = this.size() - rel.maxSize;
|
||||
if (diff > 0)
|
||||
super.removeRange(this.size()-1-diff, this.size()-1);
|
||||
}
|
||||
} else if (!colHasDefaultOrder || origin!=null) {
|
||||
// this collection is a view or the given collection doesn't have the
|
||||
// default order
|
||||
for (Iterator i = col.iterator(); i.hasNext(); ) {
|
||||
Object obj = i.next();
|
||||
if (obj==null)
|
||||
continue;
|
||||
sortIn (obj);
|
||||
cntr++;
|
||||
}
|
||||
} else {
|
||||
NodeHandle[] nhArr = (NodeHandle[]) col.toArray (new NodeHandle[0]);
|
||||
int locIdx=determineNodePosition(nhArr[0], 0); // determine start-point
|
||||
if (locIdx==-1)
|
||||
locIdx=this.size();
|
||||
// int interval=Math.max(1, this.size()/2);
|
||||
int addIdx=0;
|
||||
for (; addIdx < nhArr.length; addIdx++) {
|
||||
while (locIdx < this.size() && compareNodes(nhArr[addIdx], (NodeHandle) this.get(locIdx)) >= 0)
|
||||
locIdx++;
|
||||
if (locIdx >= this.size())
|
||||
break;
|
||||
this.add(locIdx, nhArr[addIdx]);
|
||||
cntr++;
|
||||
}
|
||||
for (; addIdx < nhArr.length; addIdx++) {
|
||||
this.add(nhArr[addIdx]);
|
||||
cntr++;
|
||||
}
|
||||
}
|
||||
return cntr;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the object specified by the given index-position
|
||||
* @param idx the index-position of the NodeHandle to remove
|
||||
*/
|
||||
public Object remove (int idx) {
|
||||
vRemove(idx);
|
||||
return super.remove(idx);
|
||||
}
|
||||
|
||||
private void vRemove(int idx) {
|
||||
if (views==null || origin!=null || views.size()<1)
|
||||
return;
|
||||
for (Iterator i = views.values().iterator(); i.hasNext(); ) {
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) i.next();
|
||||
osl.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the given Object from this List
|
||||
* @param obj the NodeHandle to remove
|
||||
*/
|
||||
public boolean remove (Object obj) {
|
||||
vRemove(obj);
|
||||
return super.remove(obj);
|
||||
}
|
||||
|
||||
private void vRemove(Object obj) {
|
||||
if (views==null || origin!=null || views.size()<1)
|
||||
return;
|
||||
for (Iterator i = views.values().iterator(); i.hasNext(); ) {
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) i.next();
|
||||
osl.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all elements conteined inside the specified collection
|
||||
* from this List
|
||||
* @param c the Collection containing all Objects to remove from this List
|
||||
* @return true if the List has been modified
|
||||
*/
|
||||
public boolean removeAll(Collection c) {
|
||||
vRemoveAll(c);
|
||||
return super.removeAll(c);
|
||||
}
|
||||
|
||||
private void vRemoveAll(Collection c) {
|
||||
if (views==null || origin!=null || views.size()<1)
|
||||
return;
|
||||
for (Iterator i = views.values().iterator(); i.hasNext(); ) {
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) i.next();
|
||||
osl.removeAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all elements from this List, which are NOT specified
|
||||
* inside the specified Collecion
|
||||
* @param c the Collection containing all Objects to keep on the List
|
||||
* @return true if the List has been modified
|
||||
*/
|
||||
public boolean retainAll (Collection c) {
|
||||
vRetainAll(c);
|
||||
return super.retainAll(c);
|
||||
}
|
||||
|
||||
private void vRetainAll(Collection c) {
|
||||
if (views==null || origin!=null || views.size()<1)
|
||||
return;
|
||||
for (Iterator i = views.values().iterator(); i.hasNext(); ) {
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) i.next();
|
||||
osl.retainAll (c);
|
||||
}
|
||||
}
|
||||
|
||||
private int determineNodePosition (NodeHandle nh, int startIdx) {
|
||||
int size = this.size();
|
||||
int interval = Math.max(1, (size-startIdx)/2);
|
||||
boolean dirUp=true;
|
||||
int cntr = 0;
|
||||
for (int i = 0; i < size
|
||||
&& (i < rel.maxSize || rel.maxSize <= 0)
|
||||
&& cntr<(size*2); cntr++) { // cntr is used to avoid endless-loops which shouldn't happen
|
||||
NodeHandle curr = (NodeHandle) this.get(i);
|
||||
int comp = compareNodes(nh, curr);
|
||||
// current NodeHandle is below the given NodeHandle
|
||||
// interval has to be 1 and
|
||||
// idx must be zero or the node before the current node must be higher or equal
|
||||
// all conditions must be met to determine the correct position of a node
|
||||
if (comp < 0 && interval==1 && (i==0 || compareNodes(nh, (NodeHandle) this.get(i-1)) >= 0)) {
|
||||
return i;
|
||||
} else if (comp < 0) {
|
||||
dirUp=false;
|
||||
} else if (comp > 0) {
|
||||
dirUp=true;
|
||||
} else if (comp == 0) {
|
||||
dirUp=true;
|
||||
interval=1;
|
||||
}
|
||||
if (dirUp) {
|
||||
i=i+interval;
|
||||
if (i >= this.size()) {
|
||||
if (compareNodes(nh, (NodeHandle) this.get(size-1)) >= 0)
|
||||
break;
|
||||
interval = Math.max(1, (i - size-1)/2);
|
||||
i = this.size()-1;
|
||||
} else {
|
||||
interval = Math.max(1,interval/2);
|
||||
}
|
||||
} else {
|
||||
i=i-interval;
|
||||
if (i < 0) { // shouldn't happen i think
|
||||
interval=Math.max(1,(interval+i)/2);
|
||||
i=0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (cntr >= size*2 && size>1) {
|
||||
System.err.println("determineNodePosition needed more than the allowed iterations" + this.rel.prototype);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two nodes depending on the specified ORDER for this collection.
|
||||
* @param nh1 the first NodeHandle
|
||||
* @param nh2 the second NodeHandle
|
||||
* @return an integer lesser than zero if nh1 is less than, zero if nh1 is equal to and a value greater than zero if nh1 is bigger than nh2.
|
||||
*/
|
||||
private int compareNodes(NodeHandle nh1, NodeHandle nh2) {
|
||||
WrappedNodeManager wnmgr=null;
|
||||
for (int i = 0; i < orderProperties.length; i++) {
|
||||
if (orderProperties[i]==null) {
|
||||
// we have the id as order-criteria-> avoid loading node
|
||||
// and compare numerically instead of lexicographically
|
||||
String s1 = nh1.getID();
|
||||
String s2 = nh2.getID();
|
||||
int j = compareNumericString (s1, s2);
|
||||
if (j==0)
|
||||
continue;
|
||||
if (orderIsDesc[i])
|
||||
j=j*-1;
|
||||
return j;
|
||||
}
|
||||
System.err.println("CHECKING PROPERTY: " + orderProperties[i] + " / " + orderIsDesc[i]);
|
||||
if (wnmgr == null)
|
||||
wnmgr = rel.otherType.getWrappedNodeManager();
|
||||
Property p1 = nh1.getNode(wnmgr).getProperty(orderProperties[i]);
|
||||
Property p2 = nh2.getNode(wnmgr).getProperty(orderProperties[i]);
|
||||
System.out.println ("*** Comparing " + p1 + " - " + p2);
|
||||
int j;
|
||||
if (p1==null && p2==null)
|
||||
continue;
|
||||
else if (p1==null)
|
||||
j = -1;
|
||||
else
|
||||
j = p1.compareTo(p2);
|
||||
if (j == 0)
|
||||
continue;
|
||||
if (orderIsDesc[i])
|
||||
j = j * -1;
|
||||
return j;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two strings containing numbers depending on their numeric values
|
||||
* instead of doing a lexicographical comparison.
|
||||
* @param a the first String
|
||||
* @param b the second String
|
||||
*/
|
||||
public static int compareNumericString(String a, String b) {
|
||||
if (a == null && b != null)
|
||||
return -1;
|
||||
if (a != null && b == null)
|
||||
return 1;
|
||||
if (a == null && b == null)
|
||||
return 0;
|
||||
if (a.length() < b.length())
|
||||
return -1;
|
||||
if (a.length() > b.length())
|
||||
return 1;
|
||||
for (int i = 0; i < a.length(); i++) {
|
||||
if (a.charAt(i) < b.charAt(i))
|
||||
return -1;
|
||||
if (a.charAt(i) > b.charAt(i))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public List getOrderedView (String order) {
|
||||
if (origin != null)
|
||||
return origin.getOrderedView(order);
|
||||
String key = order.trim().toLowerCase();
|
||||
if (key.equalsIgnoreCase(rel.order))
|
||||
return this;
|
||||
long start = System.currentTimeMillis();
|
||||
if (views == null)
|
||||
views = new HashMap();
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) views.get(key);
|
||||
if (osl == null) {
|
||||
osl = new OrderedSubnodeList (this, order, rel);
|
||||
views.put(key, osl);
|
||||
System.out.println("getting view cost me " + (System.currentTimeMillis()-start) + " millis");
|
||||
} else
|
||||
System.out.println("getting cached view cost me " + (System.currentTimeMillis()-start) + " millis");
|
||||
return osl;
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ import java.util.Date;
|
|||
* A property implementation for Nodes stored inside a database. Basically
|
||||
* the same as for transient nodes, with a few hooks added.
|
||||
*/
|
||||
public final class Property implements IProperty, Serializable, Cloneable {
|
||||
public final class Property implements IProperty, Serializable, Cloneable, Comparable {
|
||||
static final long serialVersionUID = -1022221688349192379L;
|
||||
private String propname;
|
||||
private Node node;
|
||||
|
@ -253,7 +253,12 @@ public final class Property implements IProperty, Serializable, Cloneable {
|
|||
*/
|
||||
public void setDateValue(Date date) {
|
||||
type = DATE;
|
||||
value = date;
|
||||
// normalize from java.sql.* Date subclasses
|
||||
if (date != null && date.getClass() != Date.class) {
|
||||
value = new Date(date.getTime());
|
||||
} else {
|
||||
value = date;
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
@ -483,4 +488,56 @@ public final class Property implements IProperty, Serializable, Cloneable {
|
|||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*
|
||||
* The following cases throw a ClassCastException
|
||||
* - Properties of a different type
|
||||
* - Properties of boolean or node type
|
||||
*/
|
||||
public int compareTo(Object obj) {
|
||||
Property p = (Property) obj;
|
||||
int ptype = p.getType();
|
||||
Object pvalue = p.getValue();
|
||||
|
||||
if (type==NODE || ptype==NODE ||
|
||||
type == BOOLEAN || ptype == BOOLEAN) {
|
||||
throw new ClassCastException("uncomparable values " + this + "(" + type + ") : " + p + "(" + ptype + ")");
|
||||
}
|
||||
if (value==null && pvalue == null) {
|
||||
return 0;
|
||||
} else if (value == null) {
|
||||
return -1;
|
||||
} if (pvalue == null) {
|
||||
return 1;
|
||||
}
|
||||
if (type != ptype) {
|
||||
throw new ClassCastException("uncomparable values " + this + "(" + type + ") : " + p + "(" + ptype + ")");
|
||||
|
||||
}
|
||||
if (!(value instanceof Comparable)) {
|
||||
throw new ClassCastException("uncomparable value " + value + "(" + value.getClass() + ")");
|
||||
}
|
||||
// System.err.println("COMPARING: " + value.getClass() + " TO " + pvalue.getClass());
|
||||
return ((Comparable) value).compareTo(pvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if object o is equal to this property.
|
||||
*
|
||||
* @param obj the object to compare to
|
||||
* @return true if this equals obj
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (!(obj instanceof Property))
|
||||
return false;
|
||||
Property p = (Property) obj;
|
||||
return value == null ? p.value == null : value.equals(p.value);
|
||||
}
|
||||
}
|
|
@ -87,8 +87,10 @@ public final class Relation {
|
|||
boolean aggressiveCaching;
|
||||
boolean isPrivate = false;
|
||||
boolean referencesPrimaryKey = false;
|
||||
String updateCriteria;
|
||||
String accessName; // db column used to access objects through this relation
|
||||
String order;
|
||||
boolean autoSorted = false;
|
||||
String groupbyOrder;
|
||||
String groupby;
|
||||
String prototype;
|
||||
|
@ -127,6 +129,8 @@ public final class Relation {
|
|||
this.logicalOperator = rel.logicalOperator;
|
||||
this.aggressiveLoading = rel.aggressiveLoading;
|
||||
this.aggressiveCaching = rel.aggressiveCaching;
|
||||
this.updateCriteria = rel.updateCriteria;
|
||||
this.autoSorted = rel.autoSorted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -270,6 +274,12 @@ public final class Relation {
|
|||
order = null;
|
||||
}
|
||||
|
||||
// get the criteria(s) for updating this collection
|
||||
updateCriteria = config.getProperty("updatecriteria");
|
||||
|
||||
// get the autosorting flag
|
||||
autoSorted = "auto".equalsIgnoreCase(config.getProperty("sortmode"));
|
||||
|
||||
// get additional filter property
|
||||
filter = config.getProperty("filter");
|
||||
|
||||
|
|
36
src/helma/objectmodel/db/SubnodeList.java
Normal file
36
src/helma/objectmodel/db/SubnodeList.java
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A subclass of ArrayList that adds an addSorted(Object) method to
|
||||
*/
|
||||
public class SubnodeList extends ArrayList {
|
||||
|
||||
/**
|
||||
* Inserts the specified element at the specified position in this
|
||||
* list without performing custom ordering
|
||||
*
|
||||
* @param obj element to be inserted.
|
||||
*/
|
||||
public boolean addSorted(Object obj) {
|
||||
return add(obj);
|
||||
}
|
||||
|
||||
}
|
399
src/helma/objectmodel/db/UpdateableSubnodeList.java
Normal file
399
src/helma/objectmodel/db/UpdateableSubnodeList.java
Normal file
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class UpdateableSubnodeList extends OrderedSubnodeList {
|
||||
|
||||
// the update-criteria-fields
|
||||
private final String updateCriteria[];
|
||||
// the corresponding property to this update-criteria
|
||||
private final String updateProperty[];
|
||||
// records to fetch from the db will have a lower value?
|
||||
private final boolean updateTypeDesc[];
|
||||
|
||||
// arrays representing the current borders for each update-criteria
|
||||
private Object highestValues[]=null;
|
||||
private Object lowestValues[]=null;
|
||||
|
||||
/**
|
||||
* Construct a new UpdateableSubnodeList. The Relation is needed
|
||||
* to get the information about the ORDERING and the UPDATECriteriaS
|
||||
*/
|
||||
public UpdateableSubnodeList (Relation rel) {
|
||||
super(rel);
|
||||
// check the update-criterias for updating this collection
|
||||
if (rel.updateCriteria == null) {
|
||||
// criteria-field muss vom criteria-operant getrennt werden
|
||||
// damit das update-criteria-rendering gut funktioniert
|
||||
updateCriteria = new String[1];
|
||||
updateCriteria[0]=rel.otherType.getIDField();
|
||||
updateProperty=null;
|
||||
updateTypeDesc = new boolean[1];
|
||||
updateTypeDesc[0] = false;
|
||||
highestValues = new Object[1];
|
||||
lowestValues = new Object[1];
|
||||
} else {
|
||||
String singleCriterias[] = rel.updateCriteria.split(",");
|
||||
updateCriteria = new String[singleCriterias.length];
|
||||
updateProperty = new String[singleCriterias.length];
|
||||
updateTypeDesc = new boolean[singleCriterias.length];
|
||||
highestValues = new Object[singleCriterias.length];
|
||||
lowestValues = new Object[singleCriterias.length];
|
||||
for (int i = 0; i < singleCriterias.length; i++) {
|
||||
parseCriteria (i, singleCriterias[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility-method for parsing criterias for updating this collection.
|
||||
*/
|
||||
private void parseCriteria (int idx, String criteria) {
|
||||
String criteriaParts[] = criteria.trim().split(" ");
|
||||
updateCriteria[idx]=criteriaParts[0].trim();
|
||||
updateProperty[idx] = rel.otherType.columnNameToProperty(updateCriteria[idx]);
|
||||
if (updateProperty[idx] == null
|
||||
&& !updateCriteria[idx].equalsIgnoreCase(rel.otherType.getIDField())) {
|
||||
throw new RuntimeException("updateCriteria has to be mapped as Property of this Prototype (" + updateCriteria[idx] + ")");
|
||||
}
|
||||
if (criteriaParts.length < 2) {
|
||||
// default to INCREASING or to BIDIRECTIONAL?
|
||||
updateTypeDesc[idx]=false;
|
||||
return;
|
||||
}
|
||||
String direction = criteriaParts[1].trim().toLowerCase();
|
||||
if ("desc".equals(direction)) {
|
||||
updateTypeDesc[idx]=true;
|
||||
} else {
|
||||
updateTypeDesc[idx]=false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render the criterias for fetching new nodes, which have been added to the
|
||||
* relational db.
|
||||
* @return the sql-criteria for updating this subnodelist
|
||||
* @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String)
|
||||
* @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String)
|
||||
*/
|
||||
public String getUpdateCriteria() throws SQLException, ClassNotFoundException {
|
||||
StringBuffer criteria = new StringBuffer ();
|
||||
for (int i = 0; i < updateCriteria.length; i++) {
|
||||
// if we don't know the borders ignore this criteria
|
||||
if (!updateTypeDesc[i] && highestValues[i]==null)
|
||||
continue;
|
||||
if (updateTypeDesc[i] && lowestValues[i]==null)
|
||||
continue;
|
||||
if (criteria.length() > 0) {
|
||||
criteria.append (" OR ");
|
||||
}
|
||||
renderUpdateCriteria(i, criteria);
|
||||
}
|
||||
if (criteria.length() < 1)
|
||||
return null;
|
||||
criteria.insert(0, "(");
|
||||
criteria.append(")");
|
||||
return criteria.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the current updatecriteria specified by idx and add the result to the given
|
||||
* StringBuffer (sb).
|
||||
* @param idx index of the current updatecriteria
|
||||
* @param sb the StringBuffer to append to
|
||||
* @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String)
|
||||
* @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String)
|
||||
*/
|
||||
private void renderUpdateCriteria(int idx, StringBuffer sb) throws SQLException, ClassNotFoundException {
|
||||
if (!updateTypeDesc[idx]) {
|
||||
sb.append(updateCriteria[idx]);
|
||||
sb.append(" > ");
|
||||
renderValue(idx, highestValues, sb);
|
||||
} else {
|
||||
sb.append(updateCriteria[idx]);
|
||||
sb.append(" < ");
|
||||
renderValue(idx, lowestValues, sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the value contained inside the given Array (values) at the given
|
||||
* index (idx) depending on it's SQL-Type and add the result to the given
|
||||
* StringBuffer (sb).
|
||||
* @param idx index-position of the value to render
|
||||
* @param values the values-array to operate on
|
||||
* @param sb the StringBuffer to append to
|
||||
* @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String)
|
||||
* @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String)
|
||||
*/
|
||||
private void renderValue(int idx, Object[] values, StringBuffer sb) throws SQLException, ClassNotFoundException {
|
||||
DbColumn dbc = rel.otherType.getColumn(updateCriteria[idx]);
|
||||
if (rel.otherType.getIDField().equalsIgnoreCase(updateCriteria[idx])) {
|
||||
if (rel.otherType.needsQuotes(updateCriteria[idx])) {
|
||||
sb.append ("'").append (values[idx]).append("'");
|
||||
} else {
|
||||
sb.append (values[idx]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Property p = (Property) values[idx];
|
||||
String strgVal = p.getStringValue();
|
||||
|
||||
switch (dbc.getType()) {
|
||||
case Types.DATE:
|
||||
case Types.TIME:
|
||||
case Types.TIMESTAMP:
|
||||
// use SQL Escape Sequences for JDBC Timestamps
|
||||
// (http://www.oreilly.com/catalog/jentnut2/chapter/ch02.html)
|
||||
Timestamp ts = p.getTimestampValue();
|
||||
sb.append ("{ts '");
|
||||
sb.append (ts.toString());
|
||||
sb.append ("'}");
|
||||
return;
|
||||
|
||||
case Types.BIT:
|
||||
case Types.TINYINT:
|
||||
case Types.BIGINT:
|
||||
case Types.SMALLINT:
|
||||
case Types.INTEGER:
|
||||
case Types.REAL:
|
||||
case Types.FLOAT:
|
||||
case Types.DOUBLE:
|
||||
case Types.DECIMAL:
|
||||
case Types.NUMERIC:
|
||||
case Types.VARBINARY:
|
||||
case Types.BINARY:
|
||||
case Types.LONGVARBINARY:
|
||||
case Types.LONGVARCHAR:
|
||||
case Types.CHAR:
|
||||
case Types.VARCHAR:
|
||||
case Types.OTHER:
|
||||
case Types.NULL:
|
||||
case Types.CLOB:
|
||||
default:
|
||||
if (rel.otherType.needsQuotes(updateCriteria[idx])) {
|
||||
sb.append ("'").append (strgVal).append ("'");
|
||||
} else {
|
||||
sb.append (strgVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add a new node honoring the Nodes SQL-Order and check if the borders for
|
||||
* the updateCriterias have changed by adding this node
|
||||
* @param obj the object to add
|
||||
*/
|
||||
public boolean add(Object obj) {
|
||||
// we do not have a SQL-Order and add this node on top of the list
|
||||
NodeHandle nh = (NodeHandle) obj;
|
||||
updateBorders(nh);
|
||||
return super.add(nh);
|
||||
}
|
||||
|
||||
/**
|
||||
* add a new node at the given index position and check if the
|
||||
* borders for the updateCriterias have changed
|
||||
* NOTE: this overrules the ordering (should we disallowe this?)
|
||||
* @param idx the index-position this node should be added at
|
||||
* @param obj the NodeHandle of the node which should be added
|
||||
*/
|
||||
public void add (int idx, Object obj) {
|
||||
NodeHandle nh = (NodeHandle) obj;
|
||||
super.add(idx, nh);
|
||||
updateBorders(nh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the currently added node changes the borers for the updateCriterias and
|
||||
* update them if neccessary.
|
||||
* @param nh the added Node
|
||||
*/
|
||||
private void updateBorders(NodeHandle nh) {
|
||||
Node node=null;
|
||||
for (int i = 0; i < updateCriteria.length; i++) {
|
||||
node=updateBorder(i, nh, node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given NodeHandle's node is outside of the criteria's border
|
||||
* having the given index-position (idx) and update the border if neccessary.
|
||||
* @param idx the index-position
|
||||
* @param nh the NodeHandle possible changing the border
|
||||
* @param node optional the node handled by this NodeHandler
|
||||
* @return The Node given or the Node retrieved of this NodeHandle
|
||||
*/
|
||||
private Node updateBorder(int idx, NodeHandle nh, Node node) {
|
||||
String cret = updateCriteria[idx];
|
||||
if (rel.otherType.getIDField().equals(cret)) {
|
||||
String nid = nh.getID();
|
||||
if (updateTypeDesc[idx]
|
||||
&& OrderedSubnodeList.compareNumericString(nid, (String) lowestValues[idx]) < 0) {
|
||||
lowestValues[idx]=nid;
|
||||
} else if (!updateTypeDesc[idx]
|
||||
&& OrderedSubnodeList.compareNumericString(nid, (String) highestValues[idx]) > 0) {
|
||||
highestValues[idx]=nid;
|
||||
}
|
||||
} else {
|
||||
if (node == null)
|
||||
node = nh.getNode(rel.otherType.getWrappedNodeManager());
|
||||
Property np = node.getProperty(
|
||||
rel.otherType.columnNameToProperty(cret));
|
||||
if (updateTypeDesc[idx]) {
|
||||
Property lp = (Property) lowestValues[idx];
|
||||
if (lp==null || np.compareTo(lp)<0)
|
||||
lowestValues[idx]=np;
|
||||
} else {
|
||||
Property hp = (Property) highestValues[idx];
|
||||
if (hp==null || np.compareTo(hp)>0)
|
||||
highestValues[idx]=np;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* First check which borders this node will change and rebuild
|
||||
* these if neccessary.
|
||||
* @param nh the NodeHandle of the removed Node
|
||||
*/
|
||||
private void rebuildBorders(NodeHandle nh) {
|
||||
boolean[] check = new boolean[updateCriteria.length];
|
||||
Node node = nh.getNode(rel.otherType.getWrappedNodeManager());
|
||||
for (int i = 0; i < updateCriteria.length; i++) {
|
||||
String cret = updateCriteria[i];
|
||||
if (cret.equals(rel.otherType.getIDField())) {
|
||||
check[i] = (updateTypeDesc[i]
|
||||
&& nh.getID().equals(lowestValues[i]))
|
||||
|| (!updateTypeDesc[i]
|
||||
&& nh.getID().equals(highestValues[i]));
|
||||
} else {
|
||||
Property p = node.getProperty(updateProperty[i]);
|
||||
check[i] = (updateTypeDesc[i]
|
||||
&& p.equals(lowestValues[i]))
|
||||
|| (!updateTypeDesc[i]
|
||||
&& p.equals(highestValues[i]));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < updateCriteria.length; i++) {
|
||||
if (!check[i])
|
||||
continue;
|
||||
rebuildBorder(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild all borders for all the updateCriterias
|
||||
*/
|
||||
public void rebuildBorders() {
|
||||
for (int i = 0; i < updateCriteria.length; i++) {
|
||||
rebuildBorder(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only rebuild the border for the update-criteria specified by the given
|
||||
* index-position (idx).
|
||||
* @param idx the index-position of the updateCriteria to rebuild the border for
|
||||
*/
|
||||
private void rebuildBorder(int idx) {
|
||||
if (updateTypeDesc[idx]) {
|
||||
lowestValues[idx]=null;
|
||||
} else {
|
||||
highestValues[idx]=null;
|
||||
}
|
||||
for (int i = 0; i < this.size(); i++) {
|
||||
updateBorder(idx, (NodeHandle) this.get(i), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the object specified by the given index-position
|
||||
* and update the borders if neccesary
|
||||
* @param idx the index-position of the NodeHandle to remove
|
||||
*/
|
||||
public Object remove (int idx) {
|
||||
Object obj = super.remove(idx);
|
||||
if (obj == null)
|
||||
return null;
|
||||
rebuildBorders((NodeHandle) obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the given Object from this List and update the borders if neccesary
|
||||
* @param obj the NodeHandle to remove
|
||||
*/
|
||||
public boolean remove (Object obj) {
|
||||
if (!super.remove(obj))
|
||||
return false;
|
||||
rebuildBorders((NodeHandle) obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all elements conteined inside the specified collection
|
||||
* from this List and update the borders if neccesary
|
||||
* @param c the Collection containing all Objects to remove from this List
|
||||
* @return true if the List has been modified
|
||||
*/
|
||||
public boolean removeAll(Collection c) {
|
||||
if (!super.removeAll(c))
|
||||
return false;
|
||||
for (Iterator i = c.iterator(); i.hasNext(); )
|
||||
rebuildBorders((NodeHandle) i.next());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all elements from this List, which are NOT specified
|
||||
* inside the specified Collecion and update the borders if neccesary
|
||||
* @param c the Collection containing all Objects to keep on the List
|
||||
* @return true if the List has been modified
|
||||
*/
|
||||
public boolean retainAll (Collection c) {
|
||||
if (!super.retainAll(c))
|
||||
return false;
|
||||
rebuildBorders();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the borders have to be rebuilt because of the removed or
|
||||
* the added NodeHandle.
|
||||
*/
|
||||
public Object set(int idx, Object obj) {
|
||||
Object prevObj = super.set(idx, obj);
|
||||
rebuildBorders((NodeHandle) prevObj);
|
||||
updateBorders((NodeHandle) obj);
|
||||
return prevObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* if the wrapped List is an instance of OrderedSubnodeList,
|
||||
* the sortIn-method will be used.
|
||||
*/
|
||||
public boolean addAll(Collection col) {
|
||||
return sortIn(col, true) > 0;
|
||||
}
|
||||
}
|
|
@ -104,7 +104,7 @@ public final class WrappedNodeManager {
|
|||
* @param rel
|
||||
* @return
|
||||
*/
|
||||
public List getNodes(Node home, Relation rel) {
|
||||
public SubnodeList getNodes(Node home, Relation rel) {
|
||||
try {
|
||||
return nmgr.getNodes(home, rel);
|
||||
} catch (Exception x) {
|
||||
|
@ -124,7 +124,7 @@ public final class WrappedNodeManager {
|
|||
* @param rel
|
||||
* @return
|
||||
*/
|
||||
public List getNodeIDs(Node home, Relation rel) {
|
||||
public SubnodeList getNodeIDs(Node home, Relation rel) {
|
||||
try {
|
||||
return nmgr.getNodeIDs(home, rel);
|
||||
} catch (Exception x) {
|
||||
|
@ -136,6 +136,21 @@ public final class WrappedNodeManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see helma.objectmodel.db.NodeManager#updateSubnodeList(Node, Relation)
|
||||
*/
|
||||
public int updateSubnodeList (Node home, Relation rel) {
|
||||
try {
|
||||
return nmgr.updateSubnodeList(home, rel);
|
||||
} catch (Exception x) {
|
||||
if (nmgr.app.debug()) {
|
||||
x.printStackTrace();
|
||||
}
|
||||
|
||||
throw new RuntimeException("Error retrieving NodeIDs: ", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the nodes contained in the given Node's collection
|
||||
* specified by the given Relation.
|
||||
|
|
|
@ -21,9 +21,12 @@ import helma.objectmodel.db.NodeManager;
|
|||
import helma.objectmodel.db.Node;
|
||||
import helma.framework.core.Application;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* A simple XML-database
|
||||
*/
|
||||
|
@ -125,7 +128,7 @@ public final class XmlDatabase implements IDatabase {
|
|||
throw (new DatabaseException("Error initializing db"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to copy style sheet for XML files to database directory
|
||||
*/
|
||||
|
@ -134,7 +137,7 @@ public final class XmlDatabase implements IDatabase {
|
|||
FileOutputStream out = null;
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
|
||||
|
||||
try {
|
||||
in = getClass().getResourceAsStream("helma.xsl");
|
||||
out = new FileOutputStream(destination);
|
||||
|
@ -145,9 +148,9 @@ public final class XmlDatabase implements IDatabase {
|
|||
System.err.println("Error copying db style sheet: "+iox);
|
||||
} finally {
|
||||
try {
|
||||
if (out != null)
|
||||
if (out != null)
|
||||
out.close();
|
||||
if (in != null)
|
||||
if (in != null)
|
||||
in.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
@ -181,7 +184,7 @@ public final class XmlDatabase implements IDatabase {
|
|||
if (idgen.dirty) {
|
||||
try {
|
||||
saveIDGenerator(txn);
|
||||
idgen.dirty = false;
|
||||
idgen.dirty = false;
|
||||
} catch (IOException x) {
|
||||
throw new DatabaseException(x.toString());
|
||||
}
|
||||
|
@ -266,12 +269,15 @@ public final class XmlDatabase implements IDatabase {
|
|||
throw new ObjectNotFoundException("Object not found for key " + kstr);
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
XmlDatabaseReader reader = new XmlDatabaseReader(nmgr);
|
||||
Node node = reader.read(f);
|
||||
|
||||
return node;
|
||||
} catch (Exception x) {
|
||||
} catch (ParserConfigurationException x) {
|
||||
app.logError("Error reading " +f, x);
|
||||
throw new IOException(x.toString());
|
||||
} catch (SAXException x) {
|
||||
app.logError("Error reading " +f, x);
|
||||
throw new IOException(x.toString());
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public final class XmlDatabaseReader extends DefaultHandler implements XmlConsta
|
|||
private String elementName = null;
|
||||
private StringBuffer charBuffer = null;
|
||||
Hashtable propMap = null;
|
||||
List subnodes = null;
|
||||
SubnodeList subnodes = null;
|
||||
|
||||
/**
|
||||
* Creates a new XmlDatabaseReader object.
|
||||
|
@ -121,11 +121,10 @@ public final class XmlDatabaseReader extends DefaultHandler implements XmlConsta
|
|||
|
||||
if ("hop:child".equals(qName)) {
|
||||
if (subnodes == null) {
|
||||
subnodes = new ExternalizableVector();
|
||||
currentNode.setSubnodes(subnodes);
|
||||
subnodes = currentNode.createSubnodeList();
|
||||
}
|
||||
|
||||
subnodes.add(handle);
|
||||
subnodes.addSorted(handle);
|
||||
} else if ("hop:parent".equals(qName)) {
|
||||
currentNode.setParentHandle(handle);
|
||||
} else {
|
||||
|
|
|
@ -32,7 +32,7 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class HopObject extends ScriptableObject implements Wrapper, PropertyRecorder {
|
||||
static Method hopObjCtor;
|
||||
|
@ -573,7 +573,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
|||
|
||||
while ((e != null) && e.hasMoreElements()) {
|
||||
Object obj = e.nextElement();
|
||||
if (obj != null)
|
||||
if (obj != null)
|
||||
a.add(Context.toObject(obj, core.global));
|
||||
}
|
||||
|
||||
|
@ -1102,4 +1102,46 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
|||
public void clearChangeSet() {
|
||||
changedProperties = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method represents the Java-Script-exposed function for updating Subnode-Collections.
|
||||
* The following conditions must be met to make a subnodecollection updateable.
|
||||
* .) the collection must be specified with collection.updateable=true
|
||||
* .) the id's of this collection must be in ascending order, meaning, that new records
|
||||
* do have a higher id than the last record loaded by this collection
|
||||
*/
|
||||
public int jsFunction_update() {
|
||||
if (!(node instanceof helma.objectmodel.db.Node))
|
||||
throw new RuntimeException ("update only callabel on persistent HopObjects");
|
||||
checkNode();
|
||||
helma.objectmodel.db.Node n = (helma.objectmodel.db.Node) node;
|
||||
return n.updateSubnodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view having a different order from this Node's subnodelist.
|
||||
* The underlying OrderedSubnodeList will keep those views and updates them
|
||||
* if the original collection has been updated.
|
||||
* @param expr the order (like sql-order using the properties instead)
|
||||
* @return ListViewWrapper holding the information of the ordered view
|
||||
*/
|
||||
public Object jsFunction_getOrderedView(String expr) {
|
||||
if (!(node instanceof helma.objectmodel.db.Node)) {
|
||||
throw new RuntimeException (
|
||||
"getOrderedView only callable on persistent HopObjects");
|
||||
}
|
||||
helma.objectmodel.db.Node n = (helma.objectmodel.db.Node) node;
|
||||
n.loadNodes();
|
||||
List subnodes = n.getSubnodeList();
|
||||
if (subnodes == null) {
|
||||
throw new RuntimeException (
|
||||
"getOrderedView only callable on already existing subnode-collections");
|
||||
}
|
||||
if (subnodes instanceof OrderedSubnodeList) {
|
||||
return new ListViewWrapper ((((OrderedSubnodeList) subnodes).getOrderedView(expr)),
|
||||
core, n.getDbMapping().getWrappedNodeManager(), this);
|
||||
}
|
||||
throw new RuntimeException (
|
||||
"getOrderedView only callable on OrderedSubnodeList");
|
||||
}
|
||||
}
|
||||
|
|
322
src/helma/scripting/rhino/ListViewWrapper.java
Normal file
322
src/helma/scripting/rhino/ListViewWrapper.java
Normal file
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
package helma.scripting.rhino;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.objectmodel.db.Key;
|
||||
import helma.objectmodel.db.Node;
|
||||
import helma.objectmodel.db.NodeHandle;
|
||||
import helma.objectmodel.db.OrderedSubnodeList;
|
||||
import helma.objectmodel.db.UpdateableSubnodeList;
|
||||
import helma.objectmodel.db.WrappedNodeManager;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.EvaluatorException;
|
||||
import org.mozilla.javascript.FunctionObject;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
import org.mozilla.javascript.Undefined;
|
||||
import org.mozilla.javascript.Wrapper;
|
||||
import org.mozilla.javascript.ScriptRuntime;
|
||||
|
||||
|
||||
public class ListViewWrapper extends ScriptableObject implements Wrapper, Scriptable {
|
||||
final List list;
|
||||
final RhinoCore core;
|
||||
final WrappedNodeManager wnm;
|
||||
final HopObject hObj;
|
||||
INode node;
|
||||
|
||||
ListViewWrapper (List list, RhinoCore core, WrappedNodeManager wnm, HopObject hObj) {
|
||||
if (list == null) {
|
||||
throw new IllegalArgumentException ("ListWrapper unable to wrap null list.");
|
||||
}
|
||||
this.core = core;
|
||||
this.list = list;
|
||||
this.wnm = wnm;
|
||||
this.hObj = hObj;
|
||||
this.node = hObj.node;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
int attributes = READONLY | DONTENUM | PERMANENT;
|
||||
|
||||
Method[] methods = this.getClass().getDeclaredMethods();
|
||||
for (int i=0; i<methods.length; i++) {
|
||||
String methodName = methods[i].getName();
|
||||
|
||||
if (methodName.startsWith("jsFunction_")) {
|
||||
methodName = methodName.substring(11);
|
||||
FunctionObject func = new FunctionObject(methodName,
|
||||
methods[i], this);
|
||||
this.defineProperty(methodName, func, attributes);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object unwrap() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public Object jsFunction_get(Object idxObj) {
|
||||
int idx;
|
||||
if (idxObj instanceof Number)
|
||||
return jsFunction_get(((Number) idxObj).intValue());
|
||||
else // fallback to this View's HopObject's get-function
|
||||
return hObj.jsFunction_get(idxObj);
|
||||
}
|
||||
|
||||
public Object jsFunction_get(int idx) {
|
||||
// return null if given index is out of bounds
|
||||
if (list.size() <= idx)
|
||||
return null;
|
||||
Object obj = list.get(idx);
|
||||
// return null if indexed object is null
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
// return HopObject in case of a NodeHandle
|
||||
if (obj instanceof NodeHandle) {
|
||||
HopObject hObj = new HopObject();
|
||||
hObj.init(core, ((NodeHandle) obj).getNode(wnm));
|
||||
return hObj;
|
||||
} else if (!(obj instanceof Scriptable)) {
|
||||
// do NOT wrap primitives - otherwise they'll be wrapped as Objects,
|
||||
// which makes them unusable for many purposes (e.g. ==)
|
||||
if (obj instanceof String ||
|
||||
obj instanceof Number ||
|
||||
obj instanceof Boolean) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
return Context.toObject(obj, core.global);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public Object jsFunction_getById(Object id) {
|
||||
return hObj.jsFunction_getById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch child objects from (relational) database.
|
||||
*/
|
||||
public void jsFunction_prefetchChildren(Object startArg, Object lengthArg)
|
||||
throws Exception {
|
||||
// check if we were called with no arguments
|
||||
if (startArg == Undefined.instance && lengthArg == Undefined.instance) {
|
||||
prefetchChildren(0, 1000);
|
||||
} else {
|
||||
int start = (int) ScriptRuntime.toNumber(startArg);
|
||||
int length = (int) ScriptRuntime.toNumber(lengthArg);
|
||||
prefetchChildren(start, length);
|
||||
}
|
||||
}
|
||||
|
||||
private void prefetchChildren(int start, int length) {
|
||||
if (list.size() < 1)
|
||||
return;
|
||||
if (!(node instanceof helma.objectmodel.db.Node))
|
||||
return;
|
||||
if (length < 1)
|
||||
return;
|
||||
if (start < 0)
|
||||
return;
|
||||
if (start >= list.size())
|
||||
return;
|
||||
checkNode();
|
||||
int l = Math.min(list.size() - start, length);
|
||||
if (l < 1)
|
||||
return;
|
||||
Key[] keys = new Key[l];
|
||||
for (int i = start; i<start+l; i++) {
|
||||
keys[i] = ((NodeHandle) list.get(i)).getKey();
|
||||
}
|
||||
try {
|
||||
((helma.objectmodel.db.Node) node).prefetchChildren(keys);
|
||||
} catch (Exception ignore) {
|
||||
System.err.println("Error in HopObject.prefetchChildren(): "+ignore);
|
||||
}
|
||||
}
|
||||
|
||||
public int jsFunction_size() {
|
||||
if (list==null)
|
||||
return 0;
|
||||
return list.size();
|
||||
}
|
||||
|
||||
public int jsFunction_count() {
|
||||
return jsFunction_size();
|
||||
}
|
||||
|
||||
public void jsFunction_add(Object child) {
|
||||
if (this.hObj==null)
|
||||
throw new RuntimeException("ListWrapper has no knowledge about any HopObject or collection");
|
||||
hObj.jsFunction_add(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full list of child objects in a JavaScript Array.
|
||||
* This is called by jsFunction_list() if called with no arguments.
|
||||
*
|
||||
* @return A JavaScript Array containing all child objects
|
||||
*/
|
||||
private Scriptable list() {
|
||||
checkNode();
|
||||
|
||||
ArrayList a = new ArrayList();
|
||||
for (Iterator i = list.iterator(); i.hasNext(); ) {
|
||||
NodeHandle nh = (NodeHandle) i.next();
|
||||
if (nh!=null)
|
||||
a.add(Context.toObject(nh.getNode(wnm), core.global));
|
||||
}
|
||||
return Context.getCurrentContext().newArray(core.global, a.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JS array of child objects with the given start and length.
|
||||
*
|
||||
* @return A JavaScript Array containing the specified child objects
|
||||
*/
|
||||
public Scriptable jsFunction_list(Object startArg, Object lengthArg) {
|
||||
if (startArg == Undefined.instance && lengthArg == Undefined.instance) {
|
||||
return list();
|
||||
}
|
||||
|
||||
int start = (int) ScriptRuntime.toNumber(startArg);
|
||||
int length = (int) ScriptRuntime.toNumber(lengthArg);
|
||||
|
||||
if (start < 0 || length < 0) {
|
||||
throw new EvaluatorException("Arguments must not be negative in HopObject.list(start, length)");
|
||||
}
|
||||
|
||||
checkNode();
|
||||
|
||||
prefetchChildren(start, length);
|
||||
ArrayList a = new ArrayList();
|
||||
|
||||
for (int i=start; i<start+length; i++) {
|
||||
NodeHandle nh = (NodeHandle) list.get(i);
|
||||
if (nh != null)
|
||||
a.add(Context.toObject(nh.getNode(wnm), core.global));
|
||||
}
|
||||
|
||||
return Context.getCurrentContext().newArray(core.global, a.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this object from the database.
|
||||
*/
|
||||
public boolean jsFunction_remove(Object arg) {
|
||||
if (this.hObj==null)
|
||||
throw new RuntimeException("ListWrapper has no knowledge about any HopObject or collection");
|
||||
return hObj.jsFunction_remove(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child node from this node's collection without deleting
|
||||
* it from the database.
|
||||
*/
|
||||
public boolean jsFunction_removeChild(Object child) {
|
||||
if (this.hObj==null)
|
||||
throw new RuntimeException("ListWrapper has no knowledge about any HopObject or collection");
|
||||
return hObj.jsFunction_removeChild(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the node itself or a subnode
|
||||
*/
|
||||
public boolean jsFunction_invalidate(Object childId) {
|
||||
if (this.hObj==null)
|
||||
throw new RuntimeException("ListWrapper has no knowledge about any HopObject or collection");
|
||||
return hObj.jsFunction_invalidate(childId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if node is contained in subnodes
|
||||
*/
|
||||
public int jsFunction_contains(Object obj) {
|
||||
if (obj instanceof HopObject) {
|
||||
INode n = ((HopObject) obj).node;
|
||||
if (n instanceof helma.objectmodel.db.Node)
|
||||
return list.indexOf(((helma.objectmodel.db.Node) n).getHandle());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method represents the Java-Script-exposed function for updating Subnode-Collections.
|
||||
* The following conditions must be met to make a subnodecollection updateable.
|
||||
* .) the collection must be specified with collection.updateable=true
|
||||
* .) the id's of this collection must be in ascending order, meaning, that new records
|
||||
* do have a higher id than the last record loaded by this collection
|
||||
*/
|
||||
public int jsFunction_update() {
|
||||
if (!(node instanceof helma.objectmodel.db.Node))
|
||||
throw new RuntimeException ("updateSubnodes only callabel on persistent HopObjects");
|
||||
checkNode();
|
||||
helma.objectmodel.db.Node n = (helma.objectmodel.db.Node) node;
|
||||
return n.updateSubnodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view having a different order from this Node's subnodelist.
|
||||
* The underlying OrderedSubnodeList will keep those views and updates them
|
||||
* if the original collection has been updated.
|
||||
* @param expr the order (like sql-order using the properties instead)
|
||||
* @return ListViewWrapper holding the information of the ordered view
|
||||
*/
|
||||
public Object jsFunction_getOrderedView(String expr) {
|
||||
if (!(list instanceof OrderedSubnodeList))
|
||||
throw new RuntimeException ("getOrderedView only callable on persistent HopObjects");
|
||||
checkNode();
|
||||
|
||||
OrderedSubnodeList osl = (OrderedSubnodeList) list;
|
||||
return new ListViewWrapper (osl.getOrderedView(expr), core, wnm, hObj);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (list==null)
|
||||
return "[ListWrapper{}]";
|
||||
else
|
||||
return "[ListWrapper"+ list.toString() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node has been invalidated. If so, it has to be re-fetched
|
||||
* from the db via the app's node manager.
|
||||
*/
|
||||
private final void checkNode() {
|
||||
if (node != null && node.getState() == INode.INVALID) {
|
||||
if (node instanceof helma.objectmodel.db.Node) {
|
||||
NodeHandle handle = ((helma.objectmodel.db.Node) node).getHandle();
|
||||
node = handle.getNode(core.app.getWrappedNodeManager());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "[ListWrapper]";
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue