001: /*
002: * Helma License Notice
003: *
004: * The contents of this file are subject to the Helma License
005: * Version 2.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://adele.helma.org/download/helma/license.txt
008: *
009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
010: *
011: * $RCSfile$
012: * $Author: hannes $
013: * $Revision: 8674 $
014: * $Date: 2007-11-28 16:32:09 +0100 (Mit, 28 Nov 2007) $
015: */
016: package helma.objectmodel.db;
017:
018: import java.sql.SQLException;
019: import java.sql.Timestamp;
020: import java.sql.Types;
021: import java.util.Collection;
022: import java.util.Iterator;
023:
024: public class UpdateableSubnodeList extends OrderedSubnodeList {
025:
026: // the update-criteria-fields
027: private final String updateCriteria[];
028: // the corresponding property to this update-criteria
029: private final String updateProperty[];
030: // records to fetch from the db will have a lower value?
031: private final boolean updateTypeDesc[];
032:
033: // arrays representing the current borders for each update-criteria
034: private Object highestValues[] = null;
035: private Object lowestValues[] = null;
036:
037: /**
038: * Construct a new UpdateableSubnodeList. The Relation is needed
039: * to get the information about the ORDERING and the UPDATECriteriaS
040: */
041: public UpdateableSubnodeList(WrappedNodeManager nmgr, Relation rel) {
042: super (nmgr, rel);
043: // check the update-criterias for updating this collection
044: if (rel.updateCriteria == null) {
045: // criteria-field muss vom criteria-operant getrennt werden
046: // damit das update-criteria-rendering gut funktioniert
047: updateCriteria = new String[1];
048: updateCriteria[0] = rel.otherType.getIDField();
049: updateProperty = null;
050: updateTypeDesc = new boolean[1];
051: updateTypeDesc[0] = false;
052: highestValues = new Object[1];
053: lowestValues = new Object[1];
054: } else {
055: String singleCriterias[] = rel.updateCriteria.split(",");
056: updateCriteria = new String[singleCriterias.length];
057: updateProperty = new String[singleCriterias.length];
058: updateTypeDesc = new boolean[singleCriterias.length];
059: highestValues = new Object[singleCriterias.length];
060: lowestValues = new Object[singleCriterias.length];
061: for (int i = 0; i < singleCriterias.length; i++) {
062: parseCriteria(i, singleCriterias[i]);
063: }
064: }
065: }
066:
067: /**
068: * Utility-method for parsing criterias for updating this collection.
069: */
070: private void parseCriteria(int idx, String criteria) {
071: String criteriaParts[] = criteria.trim().split(" ");
072: updateCriteria[idx] = criteriaParts[0].trim();
073: updateProperty[idx] = rel.otherType
074: .columnNameToProperty(updateCriteria[idx]);
075: if (updateProperty[idx] == null
076: && !updateCriteria[idx].equalsIgnoreCase(rel.otherType
077: .getIDField())) {
078: throw new RuntimeException(
079: "updateCriteria has to be mapped as Property of this Prototype ("
080: + updateCriteria[idx] + ")");
081: }
082: if (criteriaParts.length < 2) {
083: // default to INCREASING or to BIDIRECTIONAL?
084: updateTypeDesc[idx] = false;
085: return;
086: }
087: String direction = criteriaParts[1].trim().toLowerCase();
088: if ("desc".equals(direction)) {
089: updateTypeDesc[idx] = true;
090: } else {
091: updateTypeDesc[idx] = false;
092: }
093: }
094:
095: /**
096: * render the criterias for fetching new nodes, which have been added to the
097: * relational db.
098: * @return the sql-criteria for updating this subnodelist
099: * @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String)
100: * @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String)
101: */
102: public String getUpdateCriteria() throws SQLException,
103: ClassNotFoundException {
104: StringBuffer criteria = new StringBuffer();
105: for (int i = 0; i < updateCriteria.length; i++) {
106: // if we don't know the borders ignore this criteria
107: if (!updateTypeDesc[i] && highestValues[i] == null)
108: continue;
109: if (updateTypeDesc[i] && lowestValues[i] == null)
110: continue;
111: if (criteria.length() > 0) {
112: criteria.append(" OR ");
113: }
114: renderUpdateCriteria(i, criteria);
115: }
116: if (criteria.length() < 1)
117: return null;
118: criteria.insert(0, "(");
119: criteria.append(")");
120: return criteria.toString();
121: }
122:
123: /**
124: * Render the current updatecriteria specified by idx and add the result to the given
125: * StringBuffer (sb).
126: * @param idx index of the current updatecriteria
127: * @param sb the StringBuffer to append to
128: * @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String)
129: * @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String)
130: */
131: private void renderUpdateCriteria(int idx, StringBuffer sb)
132: throws SQLException, ClassNotFoundException {
133: if (!updateTypeDesc[idx]) {
134: sb.append(updateCriteria[idx]);
135: sb.append(" > ");
136: renderValue(idx, highestValues, sb);
137: } else {
138: sb.append(updateCriteria[idx]);
139: sb.append(" < ");
140: renderValue(idx, lowestValues, sb);
141: }
142: }
143:
144: /**
145: * Renders the value contained inside the given Array (values) at the given
146: * index (idx) depending on it's SQL-Type and add the result to the given
147: * StringBuffer (sb).
148: * @param idx index-position of the value to render
149: * @param values the values-array to operate on
150: * @param sb the StringBuffer to append to
151: * @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String)
152: * @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String)
153: */
154: private void renderValue(int idx, Object[] values, StringBuffer sb)
155: throws SQLException, ClassNotFoundException {
156: DbColumn dbc = rel.otherType.getColumn(updateCriteria[idx]);
157: if (rel.otherType.getIDField().equalsIgnoreCase(
158: updateCriteria[idx])) {
159: if (rel.otherType.needsQuotes(updateCriteria[idx])) {
160: sb.append("'").append(
161: DbMapping.escapeString(values[idx]))
162: .append("'");
163: } else {
164: sb.append(DbMapping.checkNumber(values[idx]));
165: }
166: return;
167: }
168: Property p = (Property) values[idx];
169: String strgVal = p.getStringValue();
170:
171: switch (dbc.getType()) {
172: case Types.DATE:
173: case Types.TIME:
174: case Types.TIMESTAMP:
175: // use SQL Escape Sequences for JDBC Timestamps
176: // (http://www.oreilly.com/catalog/jentnut2/chapter/ch02.html)
177: Timestamp ts = p.getTimestampValue();
178: sb.append("{ts '");
179: sb.append(ts.toString());
180: sb.append("'}");
181: return;
182:
183: case Types.BIT:
184: case Types.BOOLEAN:
185: case Types.TINYINT:
186: case Types.BIGINT:
187: case Types.SMALLINT:
188: case Types.INTEGER:
189: case Types.REAL:
190: case Types.FLOAT:
191: case Types.DOUBLE:
192: case Types.DECIMAL:
193: case Types.NUMERIC:
194: case Types.VARBINARY:
195: case Types.BINARY:
196: case Types.LONGVARBINARY:
197: case Types.LONGVARCHAR:
198: case Types.CHAR:
199: case Types.VARCHAR:
200: case Types.OTHER:
201: case Types.NULL:
202: case Types.CLOB:
203: default:
204: if (rel.otherType.needsQuotes(updateCriteria[idx])) {
205: sb.append("'").append(DbMapping.escapeString(strgVal))
206: .append("'");
207: } else {
208: sb.append(DbMapping.checkNumber(strgVal));
209: }
210: }
211: }
212:
213: /**
214: * add a new node honoring the Nodes SQL-Order and check if the borders for
215: * the updateCriterias have changed by adding this node
216: * @param obj the object to add
217: */
218: public boolean add(Object obj) {
219: // we do not have a SQL-Order and add this node on top of the list
220: NodeHandle nh = (NodeHandle) obj;
221: updateBorders(nh);
222: return super .add(nh);
223: }
224:
225: /**
226: * add a new node at the given index position and check if the
227: * borders for the updateCriterias have changed
228: * NOTE: this overrules the ordering (should we disallowe this?)
229: * @param idx the index-position this node should be added at
230: * @param obj the NodeHandle of the node which should be added
231: */
232: public void add(int idx, Object obj) {
233: NodeHandle nh = (NodeHandle) obj;
234: super .add(idx, nh);
235: updateBorders(nh);
236: }
237:
238: /**
239: * Check if the currently added node changes the borers for the updateCriterias and
240: * update them if neccessary.
241: * @param nh the added Node
242: */
243: private void updateBorders(NodeHandle nh) {
244: Node node = null;
245: for (int i = 0; i < updateCriteria.length; i++) {
246: node = updateBorder(i, nh, node);
247: }
248: }
249:
250: /**
251: * Check if the given NodeHandle's node is outside of the criteria's border
252: * having the given index-position (idx) and update the border if neccessary.
253: * @param idx the index-position
254: * @param nh the NodeHandle possible changing the border
255: * @param node optional the node handled by this NodeHandler
256: * @return The Node given or the Node retrieved of this NodeHandle
257: */
258: private Node updateBorder(int idx, NodeHandle nh, Node node) {
259: String cret = updateCriteria[idx];
260: if (rel.otherType.getIDField().equalsIgnoreCase(cret)) {
261: String nid = nh.getID();
262: if (updateTypeDesc[idx]
263: && OrderedSubnodeList.compareNumericString(nid,
264: (String) lowestValues[idx]) < 0) {
265: lowestValues[idx] = nid;
266: } else if (!updateTypeDesc[idx]
267: && OrderedSubnodeList.compareNumericString(nid,
268: (String) highestValues[idx]) > 0) {
269: highestValues[idx] = nid;
270: }
271: } else {
272: if (node == null)
273: node = nh
274: .getNode(rel.otherType.getWrappedNodeManager());
275: Property np = node.getProperty(rel.otherType
276: .columnNameToProperty(cret));
277: if (updateTypeDesc[idx]) {
278: Property lp = (Property) lowestValues[idx];
279: if (lp == null || np.compareTo(lp) < 0)
280: lowestValues[idx] = np;
281: } else {
282: Property hp = (Property) highestValues[idx];
283: if (hp == null || np.compareTo(hp) > 0)
284: highestValues[idx] = np;
285: }
286: }
287: return node;
288: }
289:
290: /**
291: * First check which borders this node will change and rebuild
292: * these if neccessary.
293: * @param nh the NodeHandle of the removed Node
294: */
295: private void rebuildBorders(NodeHandle nh) {
296: boolean[] check = new boolean[updateCriteria.length];
297: Node node = nh.getNode(rel.otherType.getWrappedNodeManager());
298: for (int i = 0; i < updateCriteria.length; i++) {
299: String cret = updateCriteria[i];
300: if (cret.equalsIgnoreCase(rel.otherType.getIDField())) {
301: check[i] = (updateTypeDesc[i] && nh.getID().equals(
302: lowestValues[i]))
303: || (!updateTypeDesc[i] && nh.getID().equals(
304: highestValues[i]));
305: } else {
306: Property p = node.getProperty(updateProperty[i]);
307: check[i] = (updateTypeDesc[i] && p
308: .equals(lowestValues[i]))
309: || (!updateTypeDesc[i] && p
310: .equals(highestValues[i]));
311: }
312: }
313: for (int i = 0; i < updateCriteria.length; i++) {
314: if (!check[i])
315: continue;
316: rebuildBorder(i);
317: }
318: }
319:
320: /**
321: * Rebuild all borders for all the updateCriterias
322: */
323: public void rebuildBorders() {
324: for (int i = 0; i < updateCriteria.length; i++) {
325: rebuildBorder(i);
326: }
327: }
328:
329: /**
330: * Only rebuild the border for the update-criteria specified by the given
331: * index-position (idx).
332: * @param idx the index-position of the updateCriteria to rebuild the border for
333: */
334: private void rebuildBorder(int idx) {
335: if (updateTypeDesc[idx]) {
336: lowestValues[idx] = null;
337: } else {
338: highestValues[idx] = null;
339: }
340: for (int i = 0; i < this .size(); i++) {
341: updateBorder(idx, (NodeHandle) this .get(i), null);
342: }
343: }
344:
345: /**
346: * remove the object specified by the given index-position
347: * and update the borders if neccesary
348: * @param idx the index-position of the NodeHandle to remove
349: */
350: public Object remove(int idx) {
351: Object obj = super .remove(idx);
352: if (obj == null)
353: return null;
354: rebuildBorders((NodeHandle) obj);
355: return obj;
356: }
357:
358: /**
359: * remove the given Object from this List and update the borders if neccesary
360: * @param obj the NodeHandle to remove
361: */
362: public boolean remove(Object obj) {
363: if (!super .remove(obj))
364: return false;
365: rebuildBorders((NodeHandle) obj);
366: return true;
367: }
368:
369: /**
370: * remove all elements conteined inside the specified collection
371: * from this List and update the borders if neccesary
372: * @param c the Collection containing all Objects to remove from this List
373: * @return true if the List has been modified
374: */
375: public boolean removeAll(Collection c) {
376: if (!super .removeAll(c))
377: return false;
378: for (Iterator i = c.iterator(); i.hasNext();)
379: rebuildBorders((NodeHandle) i.next());
380: return true;
381: }
382:
383: /**
384: * remove all elements from this List, which are NOT specified
385: * inside the specified Collecion and update the borders if neccesary
386: * @param c the Collection containing all Objects to keep on the List
387: * @return true if the List has been modified
388: */
389: public boolean retainAll(Collection c) {
390: if (!super .retainAll(c))
391: return false;
392: rebuildBorders();
393: return true;
394: }
395:
396: /**
397: * checks if the borders have to be rebuilt because of the removed or
398: * the added NodeHandle.
399: */
400: public Object set(int idx, Object obj) {
401: Object prevObj = super .set(idx, obj);
402: rebuildBorders((NodeHandle) prevObj);
403: updateBorders((NodeHandle) obj);
404: return prevObj;
405: }
406:
407: /**
408: * if the wrapped List is an instance of OrderedSubnodeList,
409: * the sortIn() method will be used.
410: */
411: public boolean addAll(Collection col) {
412: return sortIn(col, true) > 0;
413: }
414: }
|