001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/model/repository/sql/storers/CachedServiceReplyStorer.java,v 1.1.1.1 2004/03/25 12:08:36 fbellas Exp $
003: * $Revision: 1.1.1.1 $
004: * $Date: 2004/03/25 12:08:36 $
005: *
006: * =============================================================================
007: *
008: * Copyright (c) 2003, The MyPersonalizer Development Group
009: * (http://www.tic.udc.es/~fbellas/mypersonalizer/index.html) at
010: * University Of A Coruna
011: * All rights reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions are met:
015: *
016: * - Redistributions of source code must retain the above copyright notice,
017: * this list of conditions and the following disclaimer.
018: *
019: * - Redistributions in binary form must reproduce the above copyright notice,
020: * this list of conditions and the following disclaimer in the documentation
021: * and/or other materials provided with the distribution.
022: *
023: * - Neither the name of the University Of A Coruna nor the names of its
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
028: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
029: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
030: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
031: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
032: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
033: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
034: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
035: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
036: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
037: * POSSIBILITY OF SUCH DAMAGE.
038: *
039: */
040:
041: package es.udc.mypersonalizer.kernel.model.repository.sql.storers;
042:
043: import java.sql.ResultSet;
044: import java.sql.PreparedStatement;
045: import java.sql.Connection;
046: import java.sql.Timestamp;
047: import java.sql.SQLException;
048: import java.util.Map;
049: import java.util.HashMap;
050: import java.util.Collection;
051: import java.util.Iterator;
052: import java.io.Reader;
053: import java.io.BufferedReader;
054: import java.io.StringReader;
055: import java.io.IOException;
056:
057: import es.udc.mypersonalizer.kernel.log.Log;
058: import es.udc.mypersonalizer.kernel.log.LogManager;
059: import es.udc.mypersonalizer.kernel.log.LogNamingConventions;
060: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfig;
061: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfigManager;
062: import es.udc.mypersonalizer.kernel.model.repository.interfaces.ServicePropertyKey;
063: import es.udc.mypersonalizer.kernel.model.repository.interfaces.CachedServiceReply;
064: import es.udc.mypersonalizer.kernel.model.repository.sql.util.SQLOperations;
065: import es.udc.mypersonalizer.kernel.util.exceptions.InternalErrorException;
066:
067: /**
068: * This "storer" class stores and removes <code>CachedServiceReply</code>s
069: * in the database.
070: *
071: * @author Fernando Bellas
072: * @since 1.0
073: */
074: public class CachedServiceReplyStorer {
075: /*
076: * IMPORTANT. The implementation makes use of the JDBC Streaming API to
077: * access the service reply column. After many experiments and consulting
078: * many documents, the JDBC Streaming API has proven to be much more
079: * portable and robust than using the JDBC CLOB API. The streaming API seems
080: * to work with any database and does not seems to have any limit (other than
081: * that imposed by the underlying database) with regard to the size of values
082: * for the service reply column.
083: */
084:
085: /** Constant for the table name */
086: private static final String CACHED_SERVICE_REPLY_TABLE_NAME;
087:
088: /** Constant for the service identifier field */
089: private static final String SERVICE_IDENTIFIER_COLUMN_NAME;
090:
091: /** Constant for the property identifier field */
092: private static final String PROPERTY_IDENTIFIER_COLUMN_NAME;
093:
094: /** Constant for the time stamp field */
095: private static final String TIME_STAMP_COLUMN_NAME;
096:
097: /** Constant for the service reply field */
098: private static final String SERVICE_REPLY_COLUMN_NAME;
099:
100: static {
101: String serviceReplyColumnName = null;
102: String timeStampColumnName = null;
103: String serviceIdentifierColumnName = null;
104: String cachedServiceReplyTableName = null;
105: String propertyIdentifierColumnName = null;
106: try {
107: DatabaseConventionsConfig config = DatabaseConventionsConfigManager
108: .getConfig();
109: serviceReplyColumnName = config.getServiceReplyColumn();
110: timeStampColumnName = config.getTimeStampColumn();
111: serviceIdentifierColumnName = config
112: .getServiceIdentifierColumn();
113: cachedServiceReplyTableName = config
114: .getCachedServiceReplyTable();
115: propertyIdentifierColumnName = config
116: .getPropertyIdentifierColumn();
117: } catch (Exception e) {
118: Log mypersonalizerLog = LogManager
119: .getLog(LogNamingConventions.MYPERSONALIZER);
120: mypersonalizerLog.write(
121: "Could not initialize configuration for "
122: + "CachedServiceReplyStorer", e,
123: CachedServiceReplyStorer.class);
124: }
125: CACHED_SERVICE_REPLY_TABLE_NAME = cachedServiceReplyTableName;
126: SERVICE_IDENTIFIER_COLUMN_NAME = serviceIdentifierColumnName;
127: PROPERTY_IDENTIFIER_COLUMN_NAME = propertyIdentifierColumnName;
128: TIME_STAMP_COLUMN_NAME = timeStampColumnName;
129: SERVICE_REPLY_COLUMN_NAME = serviceReplyColumnName;
130: }
131: /**
132: * Maximum size of the buffer (in each iteration) when reading replies from
133: * database.
134: */
135: private final static int BUFFER_MAX_SIZE = 1024 * 4;
136:
137: /**
138: * Creates an instance of this class.
139: */
140: public CachedServiceReplyStorer() {
141: }
142:
143: /**
144: * Finds a cached service reply..
145: *
146: * @param connection the connection to the database
147: * @param key the service property key
148: * @return the service reply or <code>null</code> if the service reply
149: * was not cached
150: * @throws InternalErrorException if a failure is detected.
151: */
152: public CachedServiceReply findCachedServiceReply(
153: Connection connection, ServicePropertyKey key)
154: throws InternalErrorException {
155: /*
156: * It does not throw InstanceNotFoundException if the service reply is
157: * not on the cache as it could be previously removed.
158: */
159:
160: String query = "SELECT " + SERVICE_REPLY_COLUMN_NAME + " FROM "
161: + CACHED_SERVICE_REPLY_TABLE_NAME + " WHERE "
162: + SERVICE_IDENTIFIER_COLUMN_NAME + " = ?" + " AND "
163: + PROPERTY_IDENTIFIER_COLUMN_NAME + " = ?";
164:
165: ResultSet resultSet = null;
166: PreparedStatement prepared = null;
167: Reader replyReader = null;
168:
169: try {
170:
171: prepared = connection.prepareStatement(query);
172: prepared.setString(1, key.getServiceIdentifier());
173: prepared
174: .setLong(2, key.getPropertyIdentifier().longValue());
175: resultSet = prepared.executeQuery();
176:
177: if (resultSet.next()) {
178:
179: replyReader = resultSet.getCharacterStream(1);
180: String reply = readReply(replyReader);
181:
182: return new CachedServiceReply(key, reply);
183:
184: } else {
185: return null; // the service reply was not in the cache
186: }
187:
188: } catch (Exception e) {
189: throw new InternalErrorException(e);
190: } finally {
191: if (replyReader != null) {
192: try {
193: replyReader.close();
194: } catch (IOException e) {
195: throw new InternalErrorException(e);
196: }
197: }
198: SQLOperations.closeStatement(prepared);
199: }
200: }
201:
202: /**
203: * Finds a set of cached service replies.
204: *
205: * @param connection the connection to the database
206: * @param keys a collection of <code>ServicePropertyKey</code>s
207: * @return the service replies as pairs like (<code>ServicePropertyKey,
208: * CachedServiceReply</code>). The <code>Map</code> will not
209: * contain any rows for the replies that were not cached
210: * @throws InternalErrorException if a failure is detected.
211: */
212: public Map findCachedServiceReplies(Connection connection,
213: Collection keys) throws InternalErrorException {
214:
215: Map replies = new HashMap();
216: int numberOfKeys = keys.size();
217:
218: if (numberOfKeys == 0) {
219: return replies;
220: }
221:
222: /* Construct "queryString". */
223: String queryString = "SELECT " + SERVICE_IDENTIFIER_COLUMN_NAME
224: + ", " + PROPERTY_IDENTIFIER_COLUMN_NAME + ", "
225: + SERVICE_REPLY_COLUMN_NAME + " FROM "
226: + CACHED_SERVICE_REPLY_TABLE_NAME + " WHERE ";
227:
228: for (int i = 0; i < numberOfKeys; i++) {
229: queryString += "(" + SERVICE_IDENTIFIER_COLUMN_NAME
230: + " = ? AND " + PROPERTY_IDENTIFIER_COLUMN_NAME
231: + " = ?)";
232:
233: if (i < numberOfKeys - 1) {
234: queryString += " OR ";
235: }
236: }
237:
238: PreparedStatement prepared = null;
239: ResultSet resultSet = null;
240: Reader replyReader = null;
241: try {
242:
243: prepared = connection.prepareStatement(queryString);
244:
245: /* Fill "queryString". */
246: int preparedIndex = 1;
247: Iterator keysIterator = keys.iterator();
248: while (keysIterator.hasNext()) {
249: ServicePropertyKey key = (ServicePropertyKey) keysIterator
250: .next();
251: prepared.setString(preparedIndex++, key
252: .getServiceIdentifier());
253: prepared.setLong(preparedIndex++, key
254: .getPropertyIdentifier().longValue());
255: }
256:
257: /* Execute query. */
258: resultSet = prepared.executeQuery();
259:
260: while (resultSet.next()) {
261: String serviceIdentifier = resultSet.getString(1);
262: long propertyIdentifier = resultSet.getLong(2);
263: replyReader = resultSet.getCharacterStream(3);
264: String reply = null;
265: ServicePropertyKey key = null;
266:
267: reply = readReply(replyReader);
268:
269: key = new ServicePropertyKey(serviceIdentifier,
270: new Long(propertyIdentifier));
271:
272: replies.put(key, new CachedServiceReply(key, reply));
273: }
274:
275: return replies;
276:
277: } catch (Exception e) {
278: throw new InternalErrorException(e);
279: } finally {
280: if (replyReader != null) {
281: try {
282: replyReader.close();
283: } catch (IOException e) {
284: throw new InternalErrorException(e);
285: }
286: }
287: SQLOperations.closeStatement(prepared);
288: }
289:
290: }
291:
292: /**
293: * Removes a cached service reply.
294: *
295: * @param connection the connection to the database
296: * @param key the service property key
297: * @throws InternalErrorException if a failure is detected.
298: */
299: public void removeCachedServiceReply(Connection connection,
300: ServicePropertyKey key) throws InternalErrorException {
301: /*
302: * It does not throw NotFoundException if the service reply is not
303: * on the cache as it could be previously removed.
304: */
305:
306: String query = "DELETE FROM " + CACHED_SERVICE_REPLY_TABLE_NAME
307: + " WHERE " + SERVICE_IDENTIFIER_COLUMN_NAME + " = ?"
308: + " AND " + PROPERTY_IDENTIFIER_COLUMN_NAME + " = ?";
309:
310: PreparedStatement prepared = null;
311: int removedRows = 0;
312:
313: try {
314:
315: prepared = connection.prepareStatement(query);
316: prepared.setString(1, key.getServiceIdentifier());
317: prepared
318: .setLong(2, key.getPropertyIdentifier().longValue());
319: removedRows = prepared.executeUpdate();
320: } catch (SQLException e) {
321: throw new InternalErrorException(e);
322: } finally {
323: SQLOperations.closeStatement(prepared);
324: }
325:
326: if (removedRows > 1) {
327: throw new InternalErrorException(
328: "Non unique key fields are not allowed "
329: + "for the table name: "
330: + CACHED_SERVICE_REPLY_TABLE_NAME);
331: }
332:
333: }
334:
335: /**
336: * Updates a cached service reply. This method adds the service reply
337: * to the cache even if it was not previously cached.
338: *
339: * @param connection the connection to the database
340: * @param cachedServiceReply the new cached service reply
341: * @throws InternalErrorException if a failure is detected.
342: */
343: public void updateCachedServiceReply(Connection connection,
344: CachedServiceReply cachedServiceReply)
345: throws InternalErrorException {
346: /*
347: * It does not throw NotFoundException if the service reply is not
348: * on the cache as it could be previously removed.
349: */
350:
351: String query = "UPDATE " + CACHED_SERVICE_REPLY_TABLE_NAME
352: + " SET " + SERVICE_REPLY_COLUMN_NAME + " = ?, "
353: + TIME_STAMP_COLUMN_NAME + " = ?" + " WHERE "
354: + SERVICE_IDENTIFIER_COLUMN_NAME + " = ?" + " AND "
355: + PROPERTY_IDENTIFIER_COLUMN_NAME + " = ?";
356:
357: String serviceReply = cachedServiceReply.getServiceReply();
358:
359: /*
360: * The weblogic driver complains throwing an exception with
361: * the message "Unable to read from Reader" if the serviceReply
362: * is "". This workaround is intended to prevent this failure.
363: */
364: if (serviceReply.equals("")) {
365: serviceReply = " ";
366: }
367:
368: PreparedStatement prepared = null;
369: int updatedRows = 0;
370: ServicePropertyKey key = null;
371: StringReader reader = null;
372:
373: try {
374:
375: reader = new StringReader(serviceReply);
376: prepared = connection.prepareStatement(query);
377: prepared.setCharacterStream(1, reader, serviceReply
378: .length());
379: key = cachedServiceReply.getKey();
380:
381: /*
382: * To allow more precission the setTimestamp(int, Timestamp)
383: * method is used here. Some RDBMS ignore the milliseconds part
384: * of the Date if the setDate(int, Date) method is used.
385: * Example: Oracle8i.
386: */
387: prepared.setTimestamp(2, new Timestamp(System
388: .currentTimeMillis()));
389:
390: prepared.setString(3, key.getServiceIdentifier());
391: prepared
392: .setLong(4, key.getPropertyIdentifier().longValue());
393: updatedRows = prepared.executeUpdate();
394: } catch (SQLException e) {
395: throw new InternalErrorException(e);
396: } finally {
397: if (reader != null) {
398: reader.close();
399: }
400: SQLOperations.closeStatement(prepared);
401: }
402:
403: /* The reply was removed from cache */
404: if (updatedRows == 0) {
405: addCachedServiceReply(connection, key, serviceReply);
406: return;
407: }
408:
409: if (updatedRows > 1) {
410: throw new InternalErrorException(
411: "Non unique fields are not allowed for "
412: + "the table name: "
413: + CACHED_SERVICE_REPLY_TABLE_NAME);
414: }
415:
416: }
417:
418: /**
419: * Adds a cached service reply.
420: *
421: * @param connection the connection to the database
422: * @param key the service property key
423: * @param serviceReply the service reply
424: * @throws InternalErrorException if a failure is detected.
425: */
426: private void addCachedServiceReply(Connection connection,
427: ServicePropertyKey key, String serviceReply)
428: throws InternalErrorException {
429:
430: String query = "INSERT INTO " + CACHED_SERVICE_REPLY_TABLE_NAME
431: + " ( " + SERVICE_REPLY_COLUMN_NAME + ", "
432: + SERVICE_IDENTIFIER_COLUMN_NAME + ", "
433: + PROPERTY_IDENTIFIER_COLUMN_NAME + ", "
434: + TIME_STAMP_COLUMN_NAME + " ) VALUES ( ?, ?, ?, ? )";
435:
436: /*
437: * The weblogic driver complains throwing an exception with
438: * the message "Unable to read from Reader" if the serviceReply
439: * is "". This workaround is intended to prevent this failure.
440: * This code fragment "prepared.setNull(1, Types.CLOB)" does
441: * not work.
442: */
443: if (serviceReply.equals("")) {
444: serviceReply = " ";
445: }
446:
447: PreparedStatement prepared = null;
448: StringReader reader = null;
449: int rowsInserted = 0;
450:
451: try {
452:
453: reader = new StringReader(serviceReply);
454: prepared = connection.prepareStatement(query);
455: prepared.setCharacterStream(1, reader, serviceReply
456: .length());
457: prepared.setString(2, key.getServiceIdentifier());
458: prepared
459: .setLong(3, key.getPropertyIdentifier().longValue());
460:
461: /*
462: * To allow more precission the setTimestamp(int, Timestamp)
463: * method is used here. Some RDBMS ignore the milliseconds part
464: * of the Date if the setDate(int, Date) method is used.
465: * Example: Oracle8i.
466: */
467: prepared.setTimestamp(4, new Timestamp(System
468: .currentTimeMillis()));
469:
470: rowsInserted = prepared.executeUpdate();
471: } catch (SQLException e) {
472: throw new InternalErrorException(e);
473: } finally {
474: if (reader != null) {
475: reader.close();
476: }
477: SQLOperations.closeStatement(prepared);
478: }
479:
480: if (rowsInserted == 0) {
481: throw new InternalErrorException(
482: "No rows inserted for the table name: "
483: + CACHED_SERVICE_REPLY_TABLE_NAME);
484: }
485:
486: }
487:
488: /**
489: * Removes from the cache table all the service replies whose life time
490: * has expired. A reply expires if its life time is greather than the
491: * time period passed. Example: for a time period of 60 seconds, any
492: * service reply kept in the cache table for more that 60 seconds will be
493: * removed.
494: *
495: * @param connection the connection to the database
496: * @param timePeriod the time period in seconds
497: * @return the number of replies removed from the cache table
498: * @throws InternalErrorException if a failure is detected.
499: */
500: public int removeOldCachedServiceReplies(Connection connection,
501: int timePeriod) throws InternalErrorException {
502:
503: String query = "DELETE FROM " + CACHED_SERVICE_REPLY_TABLE_NAME
504: + " WHERE ? > " + TIME_STAMP_COLUMN_NAME;
505:
506: PreparedStatement prepared = null;
507: int removedRows = 0;
508:
509: try {
510: prepared = connection.prepareStatement(query);
511: /*
512: * ( NOW_TIMESTAMP - TIMESTAMP ) > timePeriod in millis ...
513: * ( NOW_TIMESTAMP - timePeriod in millis - TIMESTAMP ) > 0 ...
514: * ( NOW_TIMESTAMP - timePeriod in millis ) > TIMESTAMP
515: */
516: prepared.setTimestamp(1, new Timestamp(System
517: .currentTimeMillis()
518: - (timePeriod * 1000)));
519: removedRows = prepared.executeUpdate();
520: return removedRows;
521: } catch (SQLException e) {
522: throw new InternalErrorException(e);
523: } finally {
524: SQLOperations.closeStatement(prepared);
525: }
526: }
527:
528: /**
529: * Removes from the cache table all the service replies whose service
530: * identifiers match the one passed as a parameter.
531: *
532: * @param connection the connection to the database
533: * @param serviceIdentifier the service identifier
534: * @return the number of replies removed from the cache table
535: * @throws InternalErrorException if a failure is detected.
536: */
537: public int removeOldCachedServiceReplies(Connection connection,
538: String serviceIdentifier) throws InternalErrorException {
539:
540: String query = "DELETE FROM " + CACHED_SERVICE_REPLY_TABLE_NAME
541: + " WHERE " + SERVICE_IDENTIFIER_COLUMN_NAME + " = ?";
542:
543: PreparedStatement prepared = null;
544: int removedRows = 0;
545:
546: try {
547: prepared = connection.prepareStatement(query);
548: prepared.setString(1, serviceIdentifier);
549: removedRows = prepared.executeUpdate();
550: return removedRows;
551: } catch (SQLException e) {
552: throw new InternalErrorException(e);
553: } finally {
554: SQLOperations.closeStatement(prepared);
555: }
556: }
557:
558: /**
559: * Reads a service reply present in a <code>java.io.Reader</code>.
560: *
561: * @param replyReader the service reply
562: * @return the service reply
563: * @throws IOException if an I/O exception occured
564: */
565: private String readReply(Reader replyReader) throws IOException {
566:
567: StringBuffer reply = new StringBuffer();
568: BufferedReader replyBufferedReader = new BufferedReader(
569: replyReader);
570: char[] buffer = new char[BUFFER_MAX_SIZE];
571: int readSize = 0;
572:
573: do {
574:
575: readSize = replyBufferedReader.read(buffer, 0,
576: BUFFER_MAX_SIZE);
577:
578: if (readSize != -1) {
579: reply.append(buffer, 0, readSize);
580: }
581:
582: } while (readSize != -1);
583:
584: return reply.toString();
585:
586: }
587: }
|