001: package net.sourceforge.orbroker;
002:
003: import java.io.BufferedReader;
004: import java.io.IOException;
005: import java.io.InputStreamReader;
006: import java.sql.Connection;
007: import java.sql.PreparedStatement;
008: import java.sql.ResultSet;
009: import java.sql.SQLException;
010: import java.util.HashMap;
011: import java.util.Map;
012: import java.util.logging.Level;
013:
014: import net.kildenpedersen.reflect.Reflector;
015:
016: /*
017: * Created on Aug 1, 2004
018: */
019:
020: /**
021: * @author Nils Kilden-Pedersen
022: */
023: abstract class Statement {
024: /**
025: * Statement type enumeration.
026: * @author Nils Kilden-Pedersen
027: */
028: static final class Type {
029: private static final Map types = new HashMap();
030:
031: /**
032: * <a href="http://freemarker.sourceforge.net/">FreeMarker</a> statement.
033: */
034: public static final Type FREEMARKER = new Type("FreeMarker");
035: /**
036: * Static SQL statement.
037: */
038: public static final Type STATIC = new Type("static");
039: /**
040: * <a href="http://jakarta.apache.org/velocity/">Velocity</a> statement.
041: */
042: public static final Type VELOCITY = new Type("Velocity");
043:
044: static {
045: types.put(STATIC.toString(), STATIC);
046: types.put(VELOCITY.toString(), VELOCITY);
047: types.put(FREEMARKER.toString(), FREEMARKER);
048: }
049:
050: private static boolean isFreeMarker(String statement) {
051: return ((statement.indexOf("<#if") > -1 && statement
052: .indexOf("</#if>") > -1) || (statement
053: .indexOf("<#list") > -1 && statement
054: .indexOf("</#list>") > -1));
055: }
056:
057: private static boolean isVelocity(String statement) {
058: return ((statement.indexOf("#if") > -1 || statement
059: .indexOf("#foreach") > -1) && statement
060: .indexOf("#end") > -1);
061: }
062:
063: static Type evaluateStatement(String statement) {
064: if (isFreeMarker(statement)) {
065: return FREEMARKER;
066: } else if (isVelocity(statement)) {
067: return VELOCITY;
068: }
069: return STATIC;
070: }
071:
072: static Type getInstance(String name)
073: throws NoSuchFieldException {
074: if (name == null || name.trim().length() == 0) {
075: return STATIC;
076: }
077: if (!types.containsKey(name)) {
078: throw new NoSuchFieldException(
079: "Invalid statement type '" + name + "'");
080: }
081: return (Type) types.get(name);
082: }
083:
084: private final String typeName;
085:
086: private Type(String typeName) {
087: this .typeName = typeName;
088: }
089:
090: /**
091: * @inheritDoc
092: * @see java.lang.Object#toString()
093: */
094: public String toString() {
095: return this .typeName;
096: }
097: }
098:
099: private static final char[] REPLACEMENT_CHARS = new char[] { '\t',
100: '\f', '\r', '\n' };
101:
102: private static String cleanStatement(String statement) {
103: statement = statement.trim();
104: for (int i = 0; i < REPLACEMENT_CHARS.length; i++) {
105: statement = statement.replace(REPLACEMENT_CHARS[i], ' ');
106: }
107: return statement;
108: }
109:
110: private static ConfigurationException newUnknownTypeException(
111: String id, Type type) {
112: return new ConfigurationException(
113: "Code incomplete to handle statement '" + id
114: + "' of type '" + type + "'");
115: }
116:
117: /**
118: * @param externalSource
119: * @return source content
120: * @throws IOException
121: */
122: private static String readExternalSource(final String externalSource) {
123: BufferedReader reader = new BufferedReader(
124: new InputStreamReader(Statement.class
125: .getResourceAsStream(externalSource)));
126: StringBuffer buffer = new StringBuffer();
127: String line = null;
128: try {
129: while ((line = reader.readLine()) != null) {
130: buffer.append(line).append(' ');
131: }
132: } catch (IOException e) {
133: throw new ConfigurationException(e);
134: }
135: return buffer.toString();
136: }
137:
138: static Statement newInstance(String id,
139: ResultObjectDefinition resultObjectDef,
140: String externalSource) {
141:
142: String statement;
143: try {
144: statement = readExternalSource(externalSource);
145: } catch (Exception e) {
146: throw new ConfigurationException(
147: "Cannot read external source: " + externalSource, e);
148: }
149: return newInstance(id, statement, resultObjectDef);
150: }
151:
152: static Statement newInstance(String id, String statement,
153: ResultObjectDefinition resultObjectDef) {
154:
155: statement = cleanStatement(statement);
156: Statement.Type type = Statement.Type
157: .evaluateStatement(statement);
158: Statement instance;
159: if (Type.STATIC.equals(type)) {
160: if (StoredProcedureCall.isProcedureCall(statement)) {
161: instance = new StoredProcedureCall(id, statement,
162: resultObjectDef);
163: } else {
164: instance = new StaticStatement(id, statement,
165: resultObjectDef);
166: }
167: } else if (Type.VELOCITY.equals(type)) {
168: instance = new VelocityStatement(id, statement,
169: resultObjectDef);
170: } else if (Type.FREEMARKER.equals(type)) {
171: instance = new FreeMarkerStatement(id, statement,
172: resultObjectDef);
173: } else {
174: throw newUnknownTypeException(id, type);
175: }
176: return instance;
177: }
178:
179: /**
180: * Set row to map.
181: * @param map Map to set values on
182: * @param row Result row
183: */
184: static void setValuesOnMap(Map map, ResultRow row) {
185: int[] readable = row.getReadableColumns();
186: for (int i = 0; i < readable.length; i++) {
187: int column = readable[i];
188: String name = row.getColumnName(column);
189: Object value = row.getObject(column);
190: Reflector.setLeafValue(map, name, value);
191: }
192: }
193:
194: private final String id;
195: private final ResultObjectDefinition resultObjectDef;
196:
197: /**
198: * Constructor.
199: * @param id Statement ID
200: * @param resultObjectDef Result object factory
201: */
202: protected Statement(String id,
203: ResultObjectDefinition resultObjectDef) {
204: this .id = id;
205: this .resultObjectDef = resultObjectDef;
206: }
207:
208: /**
209: * @param context
210: * @param ps
211: * @param parsed
212: * @throws SQLException
213: */
214: private void attachParameterValues(ConnectionContext context,
215: PreparedStatement ps, ImmutableSQL parsed)
216: throws SQLException {
217:
218: for (int index = 0, column = 1; index < parsed
219: .getParameterCount(); index++, column++) {
220: String treePath = parsed.getParameterName(index);
221: Object parmValue = context.getParameterValue(treePath);
222: setColumnValue(ps, parmValue, column);
223: }
224: }
225:
226: private PreparedStatement buildPreparedStatement(Connection con,
227: boolean useScrollable, ImmutableSQL parsed)
228: throws SQLException, BrokerException {
229:
230: String statement = parsed.getSqlStatement();
231: logSqlStatement(statement);
232: return con.prepareStatement(statement, Broker
233: .getResultSetType(useScrollable),
234: ResultSet.CONCUR_READ_ONLY);
235: }
236:
237: /**
238: * Return the correct result-object builder.
239: * @param row
240: * @return resultObjectDef or <code>null</code> if none has been
241: * assigned.
242: * @throws ConfigurationException
243: */
244: private ResultObjectDefinition getRuntimeResultObjectDefinition(
245: ResultRow row) throws ConfigurationException {
246:
247: if (this .resultObjectDef == null) {
248: return null;
249: }
250: return this .resultObjectDef.getRuntimeResultObjectDef(row);
251: }
252:
253: private QueryResult runStatement(Connection con,
254: boolean useScrollable, ConnectionContext context,
255: ImmutableSQL runnableSQL) throws SQLException {
256:
257: java.sql.Statement stm = con.createStatement(Broker
258: .getResultSetType(useScrollable),
259: ResultSet.CONCUR_READ_ONLY);
260: String sql = runnableSQL.getSqlStatement();
261: logSqlStatement(sql);
262: ResultSet resultSet = stm.executeQuery(sql);
263: return new QueryResult(stm, resultSet, context);
264: }
265:
266: /**
267: * Build result object. This will build a result object according to
268: * the result object id specified on the statement. If no result
269: * object id has been specified, then one of the two following objects
270: * will be returned:
271: * <ol>
272: * <li>
273: * If the ResultRow contains only one column,
274: * then that column value object will be returned.
275: * </li>
276: * <li>
277: * If the ResultRow contains more than one column,
278: * then a {@link Map} will be returned containing the
279: * column names as keys, and the column value objects
280: * as values.
281: * </li>
282: * </ol>
283: * @param row Result row
284: * @return result object
285: */
286: protected final Object buildResultObject(ResultRow row) {
287:
288: ResultObjectDefinition runtimeDef = getRuntimeResultObjectDefinition(row);
289: if (runtimeDef != null) {
290: return runtimeDef.buildObject(row);
291: }
292:
293: if (row.getReadableColumns().length == 1) {
294: int column = row.getReadableColumns()[0];
295: return row.getObject(column);
296: }
297: Map map = new HashMap();
298: setValuesOnMap(map, row);
299: return map;
300:
301: }
302:
303: protected final void buildResultObject(ResultRow row,
304: Object resultObject) throws ConfigurationException {
305:
306: ResultObjectDefinition runtimeDef = getRuntimeResultObjectDefinition(row);
307: if (runtimeDef != null) {
308: runtimeDef.setValues(resultObject, row);
309: return;
310: }
311:
312: if (resultObject instanceof Map) {
313: setValuesOnMap((Map) resultObject, row);
314: return;
315: }
316:
317: String msg = "No result-object has been assigned to "
318: + toString();
319: throw new ConfigurationException(msg);
320:
321: }
322:
323: protected final void closeStatement(PreparedStatement ps) {
324:
325: try {
326: ps.close();
327: } catch (SQLException e) {
328: Broker.log(Level.WARNING, e.toString());
329: }
330: }
331:
332: protected final void logSqlStatement(String statement) {
333: if (Broker.isLoggable(Level.INFO)) {
334: StringBuffer message = new StringBuffer();
335: message.append("Executing statement '").append(this .id)
336: .append("'...\n");
337: message.append(statement);
338: Broker.log(Level.INFO, message.toString());
339: }
340: }
341:
342: protected final void setColumnValue(PreparedStatement ps,
343: Object value, int columnIndex) throws SQLException {
344: ps.setObject(columnIndex, value);
345: }
346:
347: /**
348: * @param con
349: * @param context
350: * @param batchParameterName
351: * @param batchParameters
352: * @return rows updated per parameter
353: * @throws SQLException
354: */
355: int[] executeBatch(Connection con, ConnectionContext context,
356: String batchParameterName, Object[] batchParameters)
357: throws SQLException {
358:
359: ImmutableSQL runtimeSQL = getRunnableSQL(context);
360: PreparedStatement ps = buildPreparedStatement(con, false,
361: runtimeSQL);
362:
363: try {
364: for (int i = 0; i < batchParameters.length; i++) {
365: context.setParameter(batchParameterName,
366: batchParameters[i]);
367: attachParameterValues(context, ps, runtimeSQL);
368: ps.addBatch();
369: }
370: context.removeParameter(batchParameterName);
371: return ps.executeBatch();
372: } finally {
373: closeStatement(ps);
374: }
375: }
376:
377: /**
378: * @param con
379: * @param scrollableSupport
380: * @param context
381: * @inheritDoc
382: * @return query result
383: * @throws SQLException
384: */
385: QueryResult executeQuery(Connection con, boolean scrollableSupport,
386: ConnectionContext context) throws SQLException {
387:
388: ImmutableSQL runtimeSQL = getRunnableSQL(context);
389:
390: if (!runtimeSQL.hasParameters()) {
391: return runStatement(con, scrollableSupport, context,
392: runtimeSQL);
393: }
394:
395: PreparedStatement ps = buildPreparedStatement(con,
396: scrollableSupport, runtimeSQL);
397: attachParameterValues(context, ps, runtimeSQL);
398: ResultSet rs = ps.executeQuery();
399: return new QueryResult(ps, rs, context);
400: }
401:
402: /**
403: * @param con
404: * @param context
405: * @inheritDoc
406: * @return number of records affected
407: * @throws SQLException
408: */
409: int executeUpdate(Connection con, ConnectionContext context)
410: throws SQLException {
411:
412: ImmutableSQL runtimeSQL = getRunnableSQL(context);
413: PreparedStatement ps = buildPreparedStatement(con, false,
414: runtimeSQL);
415: try {
416: attachParameterValues(context, ps, runtimeSQL);
417: return ps.executeUpdate();
418: } finally {
419: closeStatement(ps);
420: }
421: }
422:
423: /**
424: * @return Returns id.
425: */
426: final String getId() {
427: return this .id;
428: }
429:
430: abstract ImmutableSQL getRunnableSQL(ConnectionContext context)
431: throws BrokerException;
432:
433: /**
434: * @inheritDoc
435: * @see java.lang.Object#toString()
436: */
437: public final String toString() {
438: return "Statement: " + this.id;
439: }
440: }
|