001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.context;
018:
019: import java.util.AbstractList;
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Map;
024: import java.sql.SQLException;
025:
026: import velosurf.model.Entity;
027: import velosurf.util.Logger;
028: import velosurf.util.UserContext;
029:
030: /** Context wrapper for an entity.
031: *
032: * @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
033: */
034: public class EntityReference extends AbstractList {
035: /* extends AbstractList so that Velocity will call iterator() from within a #foreach directive */
036:
037: /** Builds a new EntityReference.
038: *
039: * @param entity the wrapped entity
040: */
041: public EntityReference(Entity entity) {
042: this .entity = entity;
043: }
044:
045: /** gets the name of the wrapped entity
046: */
047: public String getName() {
048: return entity.getName();
049: }
050:
051: /** Insert a new row in this entity's table.
052: *
053: * @param values col -> value map
054: * @return <code>true</code> if successfull, <code>false</code> if an error occurs (in which case $db.error can be checked).
055: */
056: public boolean insert(Map<String, Object> values) {
057: try {
058: return entity.insert(values);
059: } catch (SQLException sqle) {
060: Logger.log(sqle);
061: entity.getDB().setError(sqle.getMessage());
062: return false;
063: }
064: }
065:
066: /** Returns the ID of the last inserted row (obfuscated if needed).
067: *
068: * @return last insert ID
069: */
070: public Object getLastInsertID() {
071: long id = entity.getDB().getUserContext().getLastInsertedID(
072: entity);
073: return entity.filterID(id);
074: }
075:
076: /** <p>Update a row in this entity's table.</p>
077: *
078: * <p>Velosurf will ensure all key columns are specified, to avoid an accidental massive update.</p>
079: *
080: * @param values col -> value map
081: * @return <code>true</code> if successfull, <code>false</code> if an error occurs (in which case $db.error can be checked).
082: */
083: public boolean update(Map<String, Object> values) {
084: try {
085: return entity.update(values);
086: } catch (SQLException sqle) {
087: Logger.log(sqle);
088: entity.getDB().setError(sqle.getMessage());
089: return false;
090: }
091: }
092:
093: /** <p>Detele a row from this entity's table.</p>
094: *
095: * <p>Velosurf will ensure all key columns are specified, to avoid an accidental massive update.</p>
096: *
097: * @param values col -> value map
098: * @return <code>true</code> if successfull, <code>false</code> if an error occurs (in which case $db.error can be checked).
099: */
100: public boolean delete(Map<String, Object> values) {
101: try {
102: return entity.delete(values);
103: } catch (SQLException sqle) {
104: Logger.log(sqle);
105: entity.getDB().setError(sqle.getMessage());
106: return false;
107: }
108: }
109:
110: /** <p>Detele a row from this entity's table, specifying the value of its unique key column.</p>
111: *
112: * @param keyValue key value
113: * @return <code>true</code> if successfull, <code>false</code> if an error occurs (in which case $db.error can be checked).
114: */
115: public boolean delete(String keyValue) {
116: try {
117: return entity.delete(keyValue);
118: } catch (SQLException sqle) {
119: Logger.log(sqle);
120: entity.getDB().setError(sqle.getMessage());
121: return false;
122: }
123: }
124:
125: /** <p>Detele a row from this entity's table, specifying the value of its unique key column.</p>
126: *
127: * <p>Velosurf will ensure all key columns are specified, to avoid an accidental massive update.</p>
128: *
129: * @param keyValue key value
130: * @return <code>true</code> if successfull, <code>false</code> if an error occurs (in which case $db.error can be checked).
131: */
132: public boolean delete(Number keyValue) {
133: try {
134: return entity.delete(keyValue);
135: } catch (SQLException sqle) {
136: Logger.log(sqle);
137: entity.getDB().setError(sqle.getMessage());
138: return false;
139: }
140: }
141:
142: /** Fetch an Instance of this entity, specifying the values of its key columns in their natural order.
143: *
144: * @param values values of the key columns
145: * @return an Instance, or null if an error occured (in which case
146: * $db.error can be checked)
147: */
148: public Instance fetch(List<Object> values) {
149: try {
150: Instance instance = entity.fetch(values);
151: return instance;
152: } catch (SQLException sqle) {
153: Logger.log(sqle);
154: entity.getDB().setError(sqle.getMessage());
155: return null;
156: }
157: }
158:
159: /** Fetch an Instance of this entity, specifying the values of its key columns in the map.
160: *
161: * @param values key=>value map
162: * @return an Instance, or null if an error occured (in which case
163: * $db.error can be checked)
164: */
165: public Instance fetch(Map<String, Object> values) {
166: try {
167: Instance instance = entity.fetch(values);
168: return instance;
169: } catch (SQLException sqle) {
170: Logger.log(sqle);
171: entity.getDB().setError(sqle.getMessage());
172: return null;
173: }
174: }
175:
176: /** Fetch an Instance of this entity, specifying the value of its unique key column as a string
177: *
178: * @param keyValue value of the key column
179: * @return an Instance, or null if an error occured (in which case
180: * $db.error can be checked)
181: */
182: public Instance fetch(String keyValue) {
183: try {
184: Instance instance = entity.fetch(keyValue);
185: return instance;
186: } catch (SQLException sqle) {
187: Logger.log(sqle);
188: entity.getDB().setError(sqle.getMessage());
189: return null;
190: }
191: }
192:
193: /** Fetch an Instance of this entity, specifying the value of its unique key column as an integer
194: *
195: * @param keyValue value of the key column
196: * @return an Instance, or null if an error occured (in which case
197: * $db.error can be checked)
198: */
199: public Instance fetch(Number keyValue) {
200: try {
201: Instance instance = entity.fetch(keyValue);
202: return instance;
203: } catch (SQLException sqle) {
204: Logger.log(sqle);
205: entity.getDB().setError(sqle.getMessage());
206: return null;
207: }
208: }
209:
210: /** Called by the #foreach directive.
211: *
212: * @return a RowIterator on all instances of this entity, possibly previously
213: * refined or ordered.
214: */
215: public Iterator iterator() {
216: try {
217: RowIterator iterator = entity.query(refineCriteria, order);
218: return iterator;
219: } catch (SQLException sqle) {
220: Logger.log(sqle);
221: entity.getDB().setError(sqle.getMessage());
222: return null;
223: }
224: }
225:
226: /** Get all the rows in a list of maps.
227: *
228: * @return a list of all the rows
229: */
230: public List getRows() {
231: try {
232: RowIterator iterator = entity.query(refineCriteria, order);
233: return iterator.getRows();
234: } catch (SQLException sqle) {
235: Logger.log(sqle);
236: entity.getDB().setError(sqle.getMessage());
237: return null;
238: }
239: }
240:
241: /** <p>Refines this entity reference querying result. The provided criterium will be added to the 'where' clause (or a 'where' clause will be added).</p>
242: *
243: * <p>This method can be called several times, thus allowing a field-by-field handling of an html search form.</p>
244: *
245: * <p>All criteria will be merged with the sql 'and' operator (if there is an initial where clause, it is wrapped into parenthesis).</p>
246: *
247: * <p>Example: if we issue the following calls from inside the template:</p>
248: * <blockquote>
249: * $person.refine("age>30")
250: * <br>
251: * $person.refine("salary>3000")
252: * </blockquote>
253: * <p>the resulting query that will be issed is:</p>
254: *
255: * <p><code>select * from person where (age>30) and (salary>3000)</code></p>
256: *
257: * @param criterium a valid sql condition
258: */
259: public void refine(String criterium) {
260: Logger.trace("refine: " + criterium);
261: /* protect from SQL query injection */
262: // FIXME: check that there is an even number of "'"
263: if (/*criterium.indexOf('\'') != -1 || */criterium
264: .indexOf(';') != -1
265: || criterium.indexOf("--") != -1) {
266: Logger.error("bad refine string: " + criterium);
267: } else {
268: if (refineCriteria == null) {
269: refineCriteria = new ArrayList();
270: }
271: refineCriteria.add(criterium);
272: }
273: }
274:
275: /** Clears any refinement made on this entity.
276: */
277: public void clearRefinement() {
278: refineCriteria = null;
279: }
280:
281: /** <p>Specify an 'order by' clause for this attribute reference result.</p>
282: * <p>If an 'order by' clause is already present in the original query, the ew one is appended (but successive calls to this method overwrite previous ones).</p>
283: * <p> postfix " DESC " to a column for descending order.</p>
284: * <p>Pass it null or an empty string to clear any ordering.</p>
285: *
286: * @param order valid sql column names (separated by commas) indicating the
287: * desired order
288: */
289: public void setOrder(String order) {
290: /* protect from SQL query injection */
291: if (order.indexOf('\'') != -1 || order.indexOf(';') != -1
292: || order.indexOf("--") != -1) {
293: Logger.error("bad order string: " + order);
294: } else {
295: this .order = order;
296: }
297: }
298:
299: /** Create a new instance for this entity.
300: *
301: * @return the newly created instance
302: */
303: public Instance newInstance() {
304: Instance instance = entity.newInstance();
305: return instance;
306: }
307:
308: /** Build a new instance from a Map object.
309: *
310: * @param values the Map object containing the values
311: * @return the newly created instance
312: */
313: public Instance newInstance(Map<String, Object> values) {
314: Instance instance = entity.newInstance(values);
315: return instance;
316: }
317:
318: /**
319: * Validate values of this instance.
320: * @param values
321: * @return true if values are valid with respect to defined column constraints
322: */
323: public boolean validate(Map<String, Object> values) {
324: try {
325: return entity.validate(values);
326: } catch (SQLException sqle) {
327: Logger.error("could not check data validity!");
328: entity.getDB().getUserContext().addValidationError(
329: "internal errror");
330: Logger.log(sqle);
331: return false;
332: }
333: }
334:
335: /** Getter for the list of column names.
336: *
337: * @return the list of column names
338: */
339: public List getColumns() {
340: return entity.getColumns();
341: }
342:
343: /** Dummy method. Since this class has to appear as a Collection for Velocity, it extends the AbstractList class but only the iterator() method has a real meaning.
344: *
345: * @param i ignored
346: * @return null
347: */
348: public Object get(int i) {
349: return null;
350: }
351:
352: /** Dummy method. Since this class has to appear as a Collection for Velocity, it extends the AbstractList class but only the iterator() method has a real meaning.
353: *
354: * @return 0
355: */
356: public int size() {
357: return 0;
358: }
359:
360: /** The wrapped entity.
361: */
362: private Entity entity = null;
363:
364: /** Specified order.
365: */
366: private String order = null;
367:
368: /** Specified refining criteria.
369: */
370: private List<String> refineCriteria = null;
371:
372: }
|