001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/model/repository/sql/storers/SerializableObjectStorer.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 es.udc.mypersonalizer.kernel.log.Log;
044: import es.udc.mypersonalizer.kernel.log.LogManager;
045: import es.udc.mypersonalizer.kernel.log.LogNamingConventions;
046: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfig;
047: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfigManager;
048: import es.udc.mypersonalizer.kernel.model.repository.sql.util.SQLOperations;
049: import es.udc.mypersonalizer.kernel.util.exceptions.DuplicateInstanceException;
050: import es.udc.mypersonalizer.kernel.util.exceptions.InstanceNotFoundException;
051: import es.udc.mypersonalizer.kernel.util.exceptions.InternalErrorException;
052:
053: import java.sql.Connection;
054: import java.sql.SQLException;
055: import java.sql.ResultSet;
056: import java.sql.PreparedStatement;
057: import java.io.IOException;
058: import java.io.InputStream;
059: import java.io.ObjectInputStream;
060: import java.io.ObjectOutputStream;
061: import java.io.Serializable;
062: import java.io.ByteArrayInputStream;
063: import java.io.ByteArrayOutputStream;
064: import java.util.Collection;
065: import java.util.List;
066: import java.util.ArrayList;
067:
068: /**
069: * This "storer" class stores and removes <code>SerializableObject</code>s
070: * in the database.
071: *
072: * @author Fernando Bellas
073: * @since 1.0
074: */
075: public class SerializableObjectStorer {
076: /*
077: * IMPORTANT. The implementation makes use of the JDBC Streaming API to
078: * access the serializable object column. After many experiments and consulting
079: * many documents, the JDBC Streaming API has proven to be much more
080: * portable and robust than using the JDBC BLOB API. The streaming API seems
081: * to work with any database and does not seems to have any limit (other than
082: * that imposed by the underlying database) with regard to the size of values
083: * for the serializable object column.
084: */
085:
086: /** Constant for the table name. */
087: private static final String SERIALIZED_TABLE_NAME;
088:
089: /** Constant for the serializable object identifier field. */
090: private static final String SERIALIZED_IDENTIFIER_COLUMN_NAME;
091:
092: /** Constant for the serializable object field.*/
093: private static final String SERIALIZED_COLUMN_NAME;
094:
095: static {
096: String serializedIdentifierColumnName = null;
097: String serializedTableName = null;
098: String serializedColumnName = null;
099: try {
100: DatabaseConventionsConfig config = DatabaseConventionsConfigManager
101: .getConfig();
102: serializedIdentifierColumnName = config
103: .getSerializedIdentifierColumn();
104: serializedTableName = config.getSerializedTable();
105: serializedColumnName = config.getSerializedColumn();
106: } catch (Exception e) {
107: Log mypersonalizerLog = LogManager
108: .getLog(LogNamingConventions.MYPERSONALIZER);
109: mypersonalizerLog.write(
110: "Could not initialize configuration for "
111: + "SerializableObjectStorer", e,
112: SerializableObjectStorer.class);
113: }
114: SERIALIZED_TABLE_NAME = serializedTableName;
115: SERIALIZED_IDENTIFIER_COLUMN_NAME = serializedIdentifierColumnName;
116: SERIALIZED_COLUMN_NAME = serializedColumnName;
117: }
118:
119: /**
120: * Creates an instance of this class.
121: */
122: public SerializableObjectStorer() {
123: }
124:
125: /**
126: * Checks if the given identifier already exisits as a serializable object
127: * identifier on the database.
128: *
129: * @param connection The connection to the database
130: * @param serializableObjectIdentifier The identifier to check.
131: * @return <code>true</code> if the identifier is already in use,
132: * <code>false</code> otherwise.
133: * @throws InternalErrorException If an error occurs while checking.
134: */
135: protected boolean serializableExists(Connection connection,
136: String serializableObjectIdentifier)
137: throws InternalErrorException {
138:
139: String query = "SELECT COUNT("
140: + SERIALIZED_IDENTIFIER_COLUMN_NAME + ") FROM "
141: + SERIALIZED_TABLE_NAME + " WHERE "
142: + SERIALIZED_IDENTIFIER_COLUMN_NAME + " = ?";
143: ResultSet resultSet = null;
144: PreparedStatement prepared = null;
145:
146: try {
147: prepared = connection.prepareStatement(query);
148: prepared.setString(1, serializableObjectIdentifier);
149: resultSet = prepared.executeQuery();
150:
151: resultSet.next();
152: return resultSet.getInt(1) > 0;
153: } catch (SQLException e) {
154: throw new InternalErrorException(e);
155: } finally {
156: SQLOperations.closeStatement(prepared);
157: }
158: }
159:
160: /**
161: * Finds a serizable object.
162: *
163: * @param connection the connection to the database
164: * @param serializableObjectIdentifier the identifier of the serializable
165: * object
166: * @return the serizable object
167: * @throws InstanceNotFoundException if it was unable to find the serizable object
168: * @throws InternalErrorException if a failure is detected.
169: */
170: public SerializableObject findSerializableObject(
171: Connection connection, String serializableObjectIdentifier)
172: throws InstanceNotFoundException, InternalErrorException {
173:
174: String query = "SELECT " + SERIALIZED_COLUMN_NAME + " FROM "
175: + SERIALIZED_TABLE_NAME + " WHERE "
176: + SERIALIZED_IDENTIFIER_COLUMN_NAME + " = ?";
177: ResultSet resultSet = null;
178: PreparedStatement prepared = null;
179:
180: try {
181: prepared = connection.prepareStatement(query);
182: prepared.setString(1, serializableObjectIdentifier);
183: resultSet = prepared.executeQuery();
184:
185: if (resultSet.next()) {
186: InputStream inputStream = resultSet.getBinaryStream(1);
187: ObjectInputStream objectInputStream = null;
188: Serializable serializable = null;
189:
190: try {
191: objectInputStream = new ObjectInputStream(
192: inputStream);
193:
194: serializable = (Serializable) objectInputStream
195: .readObject();
196:
197: return new SerializableObject(
198: serializableObjectIdentifier, serializable);
199: } catch (Exception e) {
200: throw new InternalErrorException(e);
201: } finally {
202: try {
203: if (objectInputStream != null) {
204: objectInputStream.close();
205: }
206: if (inputStream != null) {
207: inputStream.close();
208: }
209: } catch (IOException e) {
210: throw new InternalErrorException(
211: "Error handling the serialized object's "
212: + "binary stream for the table "
213: + "name: "
214: + SERIALIZED_TABLE_NAME + "("
215: + e.getMessage() + ")");
216: }
217: }
218: } else {
219: throw new InstanceNotFoundException(
220: serializableObjectIdentifier,
221: SerializableObject.class.getName());
222: }
223: } catch (SQLException e) {
224: throw new InternalErrorException(e);
225: } finally {
226: SQLOperations.closeStatement(prepared);
227: }
228: }
229:
230: /**
231: * Finds serializable objects by pattern. The syntax of the pattern
232: * follows the rules of the statement LIKE of SQL.
233: *
234: * @param connection the connection to the database
235: * @param pattern the searching pattern
236: * @return the serializable objects found
237: * @throws InternalErrorException if a failure is detected.
238: */
239: public Collection findSerializableObjectsByPattern(
240: Connection connection, String pattern)
241: throws InternalErrorException {
242:
243: String query = "SELECT " + SERIALIZED_IDENTIFIER_COLUMN_NAME
244: + ", " + SERIALIZED_COLUMN_NAME + " FROM "
245: + SERIALIZED_TABLE_NAME + " WHERE "
246: + SERIALIZED_IDENTIFIER_COLUMN_NAME + " LIKE ?";
247: ResultSet resultSet = null;
248: PreparedStatement prepared = null;
249: List serializableObjects = new ArrayList();
250:
251: try {
252: String serializableObjectIdentifier = null;
253:
254: prepared = connection.prepareStatement(query);
255: prepared.setString(1, pattern);
256: resultSet = prepared.executeQuery();
257:
258: while (resultSet.next()) {
259: serializableObjectIdentifier = resultSet.getString(1);
260: InputStream inputStream = resultSet.getBinaryStream(2);
261: ObjectInputStream objectInputStream = null;
262: Serializable serializable = null;
263: SerializableObject serializableObject = null;
264:
265: try {
266: objectInputStream = new ObjectInputStream(
267: inputStream);
268: serializable = (Serializable) objectInputStream
269: .readObject();
270: serializableObject = new SerializableObject(
271: serializableObjectIdentifier, serializable);
272:
273: serializableObjects.add(serializableObject);
274:
275: } catch (Exception e) {
276: throw new InternalErrorException(e);
277: } finally {
278: try {
279: if (objectInputStream != null) {
280: objectInputStream.close();
281: }
282: if (inputStream != null) {
283: inputStream.close();
284: }
285:
286: } catch (IOException e) {
287: throw new InternalErrorException(e);
288: }
289: }
290: }
291: return serializableObjects;
292: } catch (SQLException e) {
293: throw new InternalErrorException(e);
294: } finally {
295: SQLOperations.closeStatement(prepared);
296: }
297: }
298:
299: /**
300: * Removes a serializable object.
301: *
302: * @param connection the connection to the database
303: * @param serializableObjectIdentifier the identifier of the serializable
304: * object
305: * @throws InstanceNotFoundException if it was unable to find the serializable
306: * object to be removed
307: * @throws InternalErrorException if a failure is detected.
308: */
309: public void removeSerializableObject(Connection connection,
310: String serializableObjectIdentifier)
311: throws InstanceNotFoundException, InternalErrorException {
312:
313: String query = "DELETE FROM " + SERIALIZED_TABLE_NAME
314: + " WHERE " + SERIALIZED_IDENTIFIER_COLUMN_NAME
315: + " = ?";
316: PreparedStatement prepared = null;
317: int removedRows = 0;
318:
319: try {
320: prepared = connection.prepareStatement(query);
321: prepared.setString(1, serializableObjectIdentifier);
322: removedRows = prepared.executeUpdate();
323: } catch (SQLException e) {
324: throw new InternalErrorException(e);
325: } finally {
326: SQLOperations.closeStatement(prepared);
327: }
328:
329: if (removedRows == 0) {
330: throw new InstanceNotFoundException(
331: serializableObjectIdentifier,
332: SerializableObject.class.getName());
333: }
334:
335: if (removedRows > 1) {
336: throw new InternalErrorException(
337: "Non unique key fields are not allowed "
338: + "for the table name: "
339: + SERIALIZED_TABLE_NAME);
340: }
341: }
342:
343: /**
344: * Updates a serializable object.
345: *
346: * @param connection the connection to the database
347: * @param serializableObject the new serializable object
348: * @throws InstanceNotFoundException if it was unable to find the serializable
349: * object to be updated
350: * @throws InternalErrorException if a failure is detected.
351: */
352: public void updateSerializableObject(Connection connection,
353: SerializableObject serializableObject)
354: throws InstanceNotFoundException, InternalErrorException {
355:
356: String query = "UPDATE " + SERIALIZED_TABLE_NAME + " SET "
357: + SERIALIZED_COLUMN_NAME + " = ?" + " WHERE "
358: + SERIALIZED_IDENTIFIER_COLUMN_NAME + " = ?";
359: PreparedStatement prepared = null;
360: int updatedRows = 0;
361: ByteArrayInputStream byteArrayInputStream = null;
362:
363: try {
364: byte[] serializableState = getSerializableState(serializableObject
365: .getSerializable());
366: byteArrayInputStream = new ByteArrayInputStream(
367: serializableState);
368:
369: prepared = connection.prepareStatement(query);
370: prepared.setBinaryStream(1, byteArrayInputStream,
371: serializableState.length);
372: prepared.setString(2, serializableObject
373: .getSerializableObjectIdentifier());
374: updatedRows = prepared.executeUpdate();
375:
376: } catch (Exception e) {
377: throw new InternalErrorException(e);
378: } finally {
379: SQLOperations.closeStatement(prepared);
380: try {
381: if (byteArrayInputStream != null) {
382: byteArrayInputStream.close();
383: }
384: } catch (IOException ioe) {
385: throw new InternalErrorException(ioe);
386: }
387: }
388:
389: if (updatedRows == 0) {
390: throw new InstanceNotFoundException(serializableObject
391: .getSerializableObjectIdentifier(),
392: serializableObject.getClass().getName());
393: }
394:
395: if (updatedRows > 1) {
396: throw new InternalErrorException(
397: "Non unique fields are not allowed for "
398: + "the table name: "
399: + SERIALIZED_TABLE_NAME);
400: }
401: }
402:
403: /**
404: * Adds a serializable object.
405: *
406: * @param connection the connection to the database
407: * @param serializableObject the serializable object
408: * @throws DuplicateInstanceException if the serializable object
409: * already exists
410: * @throws InternalErrorException if a failure is detected.
411: */
412: public void addSerializableObject(Connection connection,
413: SerializableObject serializableObject)
414: throws InternalErrorException, DuplicateInstanceException {
415:
416: String serializableObjectIdentifier = serializableObject
417: .getSerializableObjectIdentifier();
418:
419: if (serializableExists(connection, serializableObjectIdentifier)) {
420: throw new DuplicateInstanceException(
421: serializableObjectIdentifier, serializableObject
422: .getClass().getName());
423: } else {
424: String query = "INSERT INTO " + SERIALIZED_TABLE_NAME
425: + " ( " + SERIALIZED_IDENTIFIER_COLUMN_NAME + ", "
426: + SERIALIZED_COLUMN_NAME + " ) VALUES ( ?, ? )";
427: PreparedStatement prepared = null;
428: int rowsInserted = 0;
429: ByteArrayInputStream byteArrayInputStream = null;
430: try {
431: byte[] serializableState = getSerializableState(serializableObject
432: .getSerializable());
433: byteArrayInputStream = new ByteArrayInputStream(
434: serializableState);
435:
436: prepared = connection.prepareStatement(query);
437: prepared.setString(1, serializableObjectIdentifier);
438: prepared.setBinaryStream(2, byteArrayInputStream,
439: serializableState.length);
440:
441: rowsInserted = prepared.executeUpdate();
442: } catch (Exception e) {
443: throw new InternalErrorException(e);
444: } finally {
445: SQLOperations.closeStatement(prepared);
446: try {
447: if (byteArrayInputStream != null) {
448: byteArrayInputStream.close();
449: }
450: } catch (IOException ioe) {
451: throw new InternalErrorException(ioe);
452: }
453: }
454: if (rowsInserted == 0) {
455: throw new InternalErrorException(
456: "No rows inserted for the table " + "name: "
457: + SERIALIZED_TABLE_NAME);
458: }
459: }
460: }
461:
462: /**
463: * Returns the state of a given serializable object as a
464: * <code>byte[]</code>.
465: *
466: * @param serializable the serializable object
467: * @return the array of bytes making up the object state
468: * @throws IOException if an I/O exception occured
469: */
470: private byte[] getSerializableState(Serializable serializable)
471: throws IOException {
472:
473: ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
474: ObjectOutputStream objectOutputStream = null;
475:
476: try {
477: objectOutputStream = new ObjectOutputStream(
478: byteArrayOutputStream);
479:
480: objectOutputStream.writeObject(serializable);
481: objectOutputStream.flush();
482:
483: } finally {
484: if (objectOutputStream != null) {
485: objectOutputStream.close();
486: }
487: byteArrayOutputStream.close();
488: }
489: return byteArrayOutputStream.toByteArray();
490: }
491: }
|