* Merging updateable_collections branch (with a few changes along the way)

This commit is contained in:
hns 2006-02-09 16:35:32 +00:00
parent d4ac3d2726
commit 852543386c
13 changed files with 1607 additions and 115 deletions

View file

@ -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));
}
}

View file

@ -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);
}
}
}

View file

@ -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;

View 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;
}
}

View file

@ -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);
}
}

View file

@ -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");

View 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);
}
}

View 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;
}
}

View file

@ -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.

View file

@ -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());
}

View file

@ -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 {

View file

@ -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");
}
}

View 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]";
}
}