001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/model/repository/sql/storers/SerializationPropertyStorageStrategy.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.util.Map;
044: import java.util.HashMap;
045: import java.util.List;
046: import java.util.Iterator;
047: import java.sql.SQLException;
048: import java.sql.Connection;
049: import java.sql.ResultSet;
050: import java.sql.Statement;
051: import java.sql.PreparedStatement;
052: import java.sql.Blob;
053: import java.io.ObjectOutputStream;
054: import java.io.ObjectInputStream;
055: import java.io.ByteArrayOutputStream;
056: import java.io.ByteArrayInputStream;
057: import java.io.InputStream;
058: import java.io.IOException;
059:
060: import es.udc.mypersonalizer.kernel.log.Log;
061: import es.udc.mypersonalizer.kernel.log.LogManager;
062: import es.udc.mypersonalizer.kernel.log.LogNamingConventions;
063: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfig;
064: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfigManager;
065: import es.udc.mypersonalizer.kernel.model.properties.Property;
066: import es.udc.mypersonalizer.kernel.model.repository.sql.util.SQLOperations;
067: import es.udc.mypersonalizer.kernel.util.exceptions.DuplicateInstanceException;
068: import es.udc.mypersonalizer.kernel.util.exceptions.InstanceNotFoundException;
069: import es.udc.mypersonalizer.kernel.util.exceptions.InternalErrorException;
070: import es.udc.mypersonalizer.kernel.model.annotators.serialization.SerializationPersistenceTypeAnnotationHelper;
071: import es.udc.mypersonalizer.kernel.model.metainfo.MetaService;
072:
073: /**
074: * This class defines a policy to store and retrieve properties on a
075: * database by serializing them. Each property is kept in a database table,
076: * in a single row. This class assumes that an underlying table with
077: * appropriate fields for the key and the column
078: * {@link DatabaseConventionsConfig#getSerializedColumn()}. For example:
079: * <p>
080: * <table border="1">
081: * <tr>
082: * <td><b>key field 1</b></td>
083: * <td><b>key field 2</b></td>
084: * <td><b>key field 3</b></td>
085: * <td><b>serialized</b></td>
086: * </tr>
087: * <tr>
088: * <td>a11</td>
089: * <td>a12</td>
090: * <td>a13</td>
091: * <td>property a1</td>
092: * </tr>
093: * <tr>
094: * <td>a21</td>
095: * <td>a22</td>
096: * <td>a23</td>
097: * <td>property a2</td>
098: * </tr>
099: * </table>
100: *
101: * @author Fernando Bellas
102: * @author Abel Muinho
103: * @since 1.0
104: */
105: public class SerializationPropertyStorageStrategy implements
106: PropertyStorageStrategy {
107:
108: /** The column that contains the properties serialized. */
109: private static final String SERIALIZED_COLUMN_NAME;
110:
111: static {
112: String serializedColumnName = null;
113: try {
114: DatabaseConventionsConfig config = DatabaseConventionsConfigManager
115: .getConfig();
116: serializedColumnName = config.getSerializedColumn();
117: } catch (Exception e) {
118: Log mypersonalizerLog = LogManager
119: .getLog(LogNamingConventions.MYPERSONALIZER);
120: mypersonalizerLog.write(
121: "Could not initialize configuration for "
122: + "SerializationPropertyStorageStrategy",
123: e, SerializationPropertyStorageStrategy.class);
124: }
125: SERIALIZED_COLUMN_NAME = serializedColumnName;
126: }
127:
128: /** The table name where the serialized property is stored. */
129: private String tableName;
130:
131: public void setMetainfo(MetaService metainfo) {
132: this .tableName = SerializationPersistenceTypeAnnotationHelper
133: .getTableNameAnnotation(metainfo);
134: }
135:
136: public Property findProperty(Connection connection, Map key)
137: throws InternalErrorException, InstanceNotFoundException {
138:
139: PreparedStatement preparedStatement = null;
140:
141: try {
142: preparedStatement = createPreparedStatementForSelectDelete(
143: connection, key, true);
144:
145: ResultSet resultSet = preparedStatement.executeQuery();
146:
147: if (!resultSet.next()) {
148: throw new InstanceNotFoundException(key, Property.class
149: .getName());
150: }
151:
152: return readProperty(resultSet);
153: } catch (SQLException e) {
154: throw new InternalErrorException(e);
155: } finally {
156: SQLOperations.closeStatement(preparedStatement);
157: }
158: }
159:
160: protected boolean propertyExists(Connection connection, Map key)
161: throws InternalErrorException {
162:
163: PreparedStatement preparedStatement = null;
164:
165: try {
166: preparedStatement = createPreparedStatementForSelectDelete(
167: connection, key, true);
168:
169: ResultSet resultSet = preparedStatement.executeQuery();
170:
171: return resultSet.next();
172: } catch (SQLException e) {
173: throw new InternalErrorException(e);
174: } finally {
175: SQLOperations.closeStatement(preparedStatement);
176: }
177: }
178:
179: public Map findPropertiesByRange(Connection connection, List keys,
180: int offset, int size) throws InternalErrorException {
181:
182: Statement statement = null;
183: Map propertyRange = new HashMap();
184: int elements = 0;
185: String query = getSelectRangeQuery(keys, true); // include serialized
186:
187: try {
188: statement = connection.createStatement(
189: ResultSet.TYPE_SCROLL_INSENSITIVE,
190: ResultSet.CONCUR_READ_ONLY);
191: ResultSet resultSet = statement.executeQuery(query);
192:
193: /* Ignore the rows specified by the offset */
194: if (offset > 0) { // offset = 0 is not allowed
195: resultSet.absolute(offset);
196: }
197:
198: while (resultSet.next() && elements++ < size) {
199: Map map = readKeys(keys, resultSet);
200: Property property = readProperty(resultSet);
201: propertyRange.put(map, property);
202: }
203:
204: return propertyRange;
205: } catch (SQLException e) {
206: throw new InternalErrorException(e);
207: } finally {
208: SQLOperations.closeStatement(statement);
209: }
210: }
211:
212: public void updateProperty(Connection connection, Map key,
213: Property property) throws InternalErrorException,
214: InstanceNotFoundException {
215:
216: PreparedStatement preparedStatement = null;
217: int rowsUpdated = 0;
218:
219: preparedStatement = createPreparedStatementForInsertUpdate(
220: connection, key, property, false);
221: try {
222: rowsUpdated = preparedStatement.executeUpdate();
223: } catch (SQLException e) {
224: throw new InternalErrorException(e);
225: } finally {
226: SQLOperations.closeStatement(preparedStatement);
227: }
228:
229: if (rowsUpdated == 0) {
230: throw new InstanceNotFoundException(key, property
231: .getClass().getName());
232: }
233:
234: if (rowsUpdated > 1) {
235: throw new InternalErrorException(
236: "Non unique key fields are not allowed "
237: + "for the table: " + tableName);
238: }
239:
240: }
241:
242: public void addProperty(Connection connection, Map key,
243: Property property) throws InternalErrorException,
244: DuplicateInstanceException {
245:
246: if (propertyExists(connection, key)) {
247: throw new DuplicateInstanceException(key, property
248: .getClass().getName());
249: }
250:
251: PreparedStatement preparedStatement = null;
252: int rowsInserted = 0;
253:
254: try {
255: preparedStatement = createPreparedStatementForInsertUpdate(
256: connection, key, property, true);
257: rowsInserted = preparedStatement.executeUpdate();
258: } catch (SQLException e) {
259: throw new InternalErrorException(e);
260: } finally {
261: SQLOperations.closeStatement(preparedStatement);
262: }
263:
264: if (rowsInserted == 0) {
265: throw new InternalErrorException(
266: "No rows inserted for the table " + "name: "
267: + tableName + " and key: " + key);
268: }
269:
270: }
271:
272: public void removeProperty(Connection connection, Map key)
273: throws InternalErrorException, InstanceNotFoundException {
274:
275: PreparedStatement preparedStatement = null;
276: int rowsRemoved;
277:
278: try {
279: preparedStatement = createPreparedStatementForSelectDelete(
280: connection, key, false);
281: rowsRemoved = preparedStatement.executeUpdate();
282: } catch (SQLException e) {
283: throw new InternalErrorException(e);
284: } finally {
285: SQLOperations.closeStatement(preparedStatement);
286: }
287:
288: if (rowsRemoved == 0) {
289: throw new InstanceNotFoundException(key, Property.class
290: .getName());
291: }
292:
293: if (rowsRemoved > 1) {
294: throw new InternalErrorException(
295: "Non unique key fields are not allowed "
296: + "for table name: " + tableName);
297: }
298:
299: }
300:
301: /**
302: * Creates a <code>PreparedStatement</code> from a property (for updating
303: * or inserting) and its key. The <code>PreparedStatement</code> can be
304: * used to insert a property in the database, or to update it, depending
305: * on the value of <code>inserting</code>.
306: *
307: * @param connection the connection to the database
308: * @param key the map with the key columns and their corresponding values
309: * @param property the property to be stored
310: * @param inserting <code>true</code> if the property is to be inserted;
311: * <code>false</code> if the property is to be updated
312: * @return the prepared statement
313: * @throws InternalErrorException if it was unable to create the
314: * <code>PreparedStatement</code>
315: */
316: private PreparedStatement createPreparedStatementForInsertUpdate(
317: Connection connection, Map key, Property property,
318: boolean inserting) throws InternalErrorException {
319:
320: PreparedStatement preparedStatement = null;
321: ByteArrayOutputStream byteArrayOutputStream = null;
322: ByteArrayInputStream byteArrayInputStream = null;
323: ObjectOutputStream objectOutputStream = null;
324: String query = null;
325: int fieldNumber;
326: byte[] propertyState;
327:
328: /* Create the query. */
329: Iterator keysIterator = key.keySet().iterator();
330: if (inserting) {
331: query = getInsertQuery(keysIterator);
332: } else {
333: query = getUpdateQuery(keysIterator);
334: }
335:
336: /* Obtain "propertyState". */
337: try {
338: byteArrayOutputStream = new ByteArrayOutputStream();
339: objectOutputStream = new ObjectOutputStream(
340: byteArrayOutputStream);
341: objectOutputStream.writeObject(property);
342: objectOutputStream.flush();
343: propertyState = byteArrayOutputStream.toByteArray();
344: } catch (IOException e) {
345: throw new InternalErrorException(e);
346: } finally {
347: try {
348: if (objectOutputStream != null) {
349: objectOutputStream.close();
350: }
351: byteArrayOutputStream.close();
352: } catch (IOException e) {
353: throw new InternalErrorException(e);
354: }
355: }
356:
357: /*
358: * Create a "PreparedStatement" from "query", filling "?"s in
359: * "preparedStatement. Note that "byteArrayInputStream"
360: * must *NOT* be closed, because it will be used by the returned
361: * "prepareStatement".
362: */
363: try {
364: preparedStatement = connection.prepareStatement(query);
365: byteArrayInputStream = new ByteArrayInputStream(
366: propertyState);
367: preparedStatement.setBinaryStream(1, byteArrayInputStream,
368: propertyState.length);
369:
370: fieldNumber = 2;
371: keysIterator = key.keySet().iterator();
372: while (keysIterator.hasNext()) {
373: Object value = key.get(keysIterator.next());
374: preparedStatement.setObject(fieldNumber, value);
375: fieldNumber += 1;
376: }
377: } catch (Exception e) {
378: throw new InternalErrorException(e);
379: } finally {
380: SQLOperations.closeStatement(preparedStatement);
381: }
382:
383: return preparedStatement;
384: }
385:
386: /**
387: * Creates a <code>PreparedStatement</code> from a key (for selecting
388: * or deleting). The <code>PreparedStatement</code> can be
389: * used to search or delete the property identified by the given key,
390: * depending on the value of <code>selecting</code>.
391: *
392: * @param connection the connection to the database
393: * @param key the map with the key columns and their corresponding values
394: * @param selecting <code>true</code> if the prepared statement is to be
395: * used to search a property, <code>false</code> if the prepared
396: * statement is to be used for deleting a property
397: * @return the prepared statement
398: * @throws InternalErrorException if it was unable to create the
399: * <code>PreparedStatement</code>
400: */
401: private PreparedStatement createPreparedStatementForSelectDelete(
402: Connection connection, Map key, boolean selecting)
403: throws InternalErrorException {
404:
405: String query;
406:
407: /* Create the query. */
408: Iterator keysIterator = key.keySet().iterator();
409: if (selecting) {
410: query = getSelectQuery(keysIterator);
411: } else {
412: query = getDeleteQuery(keysIterator);
413: }
414:
415: /*
416: * Create a "PreparedStatement" from "query", filling "?"s in
417: * "preparedStatement".
418: */
419: PreparedStatement preparedStatement = null;
420: try {
421: preparedStatement = connection.prepareStatement(query);
422:
423: int fieldNumber = 1;
424: keysIterator = key.keySet().iterator();
425: while (keysIterator.hasNext()) {
426: Object value = key.get(keysIterator.next());
427: preparedStatement.setObject(fieldNumber, value);
428: fieldNumber += 1;
429: }
430:
431: } catch (SQLException e) {
432: throw new InternalErrorException(e);
433: } finally {
434: SQLOperations.closeStatement(preparedStatement);
435: }
436: return preparedStatement;
437: }
438:
439: /**
440: * Creates an update query from the key names. In the returned query,
441: * the first "?" corresponds to the serialized field column name, and the
442: * rest of them to the key names.
443: *
444: * @param keysIterator an iterator over the key names
445: * @return the update query
446: */
447: private String getUpdateQuery(Iterator keysIterator) {
448:
449: String query = "UPDATE " + tableName + " SET "
450: + SERIALIZED_COLUMN_NAME + " = ? WHERE ";
451: while (keysIterator.hasNext()) {
452: String aKey = (String) keysIterator.next();
453: query += aKey + " = ?";
454: if (keysIterator.hasNext()) {
455: query += " AND ";
456: }
457:
458: }
459:
460: return query;
461: }
462:
463: /**
464: * Creates an insert query from the key names. In the returned query, the
465: * first "?" corresponds to the serialized field column name, and the
466: * rest of them to the key names.
467: *
468: * @param keysIterator an iterator over the key names
469: * @return the insert query
470: */
471: private String getInsertQuery(Iterator keysIterator) {
472:
473: String fieldsSubquery = "INSERT INTO " + tableName + " ("
474: + SERIALIZED_COLUMN_NAME + ", ";
475: String valuesSubquery = " VALUES (?, ";
476: while (keysIterator.hasNext()) {
477: String aKey = (String) keysIterator.next();
478: fieldsSubquery += aKey;
479: valuesSubquery += "?";
480: if (keysIterator.hasNext()) {
481: fieldsSubquery += ", ";
482: valuesSubquery += ", ";
483: } else {
484: fieldsSubquery += ")";
485: valuesSubquery += ")";
486: }
487: }
488:
489: return fieldsSubquery + valuesSubquery;
490: }
491:
492: /**
493: * Creates a delete query from the key names. In the returned query, the
494: * "?"s correspond to the key names.
495: *
496: * @param keysIterator an iterator over the key names
497: * @return the delete query
498: */
499: private String getDeleteQuery(Iterator keysIterator) {
500:
501: String query = "DELETE FROM " + tableName + " WHERE ";
502: while (keysIterator.hasNext()) {
503: String aKey = (String) keysIterator.next();
504: query += aKey + " = ?";
505: if (keysIterator.hasNext()) {
506: query += " AND ";
507: }
508: }
509:
510: return query;
511: }
512:
513: /**
514: * Creates a select query (selecting the serialized field) from the key
515: * names. In the returned query, the "?"s correspond to the key names.
516: *
517: * @param keysIterator an iterator over the key names
518: * @return the select query
519: */
520: private String getSelectQuery(Iterator keysIterator) {
521:
522: String query = "SELECT " + SERIALIZED_COLUMN_NAME + " FROM "
523: + tableName + " WHERE ";
524: while (keysIterator.hasNext()) {
525: String aKey = (String) keysIterator.next();
526: query += aKey + " = ?";
527: if (keysIterator.hasNext()) {
528: query += " AND ";
529: }
530: }
531:
532: return query;
533: }
534:
535: /**
536: * Creates a select query (selecting the serialized field and the key
537: * fields) from the keys.
538: *
539: * @param keys the key fields
540: * @param selectSerializedField <code>true</code> if the serialized field
541: * must be included in the query, <code>false</code> otherwise.
542: * @return the select query
543: */
544: private String getSelectRangeQuery(List keys,
545: boolean selectSerializedField) {
546:
547: String keyFields = "";
548: for (int i = 0; i < keys.size(); i++) {
549: keyFields += (String) keys.get(i);
550: if (i != (keys.size() - 1)) {
551: keyFields += ", ";
552: }
553: }
554:
555: return "SELECT "
556: + keyFields
557: + (selectSerializedField ? ", "
558: + SERIALIZED_COLUMN_NAME : "") + " FROM "
559: + tableName + " ORDER BY " + keyFields;
560: }
561:
562: /**
563: * Reads a property from the result set.
564: *
565: * @param resultSet the result set
566: * @return the property
567: * @throws InternalErrorException if a database failure occured
568: */
569: private Property readProperty(ResultSet resultSet)
570: throws InternalErrorException {
571:
572: InputStream inputStream = null;
573: ObjectInputStream objectInputStream = null;
574:
575: try {
576: /* Retrieve the serialized property. */
577: Blob blob = resultSet.getBlob(SERIALIZED_COLUMN_NAME);
578: inputStream = blob.getBinaryStream();
579: objectInputStream = new ObjectInputStream(inputStream);
580:
581: return (Property) objectInputStream.readObject();
582:
583: } catch (Exception e) {
584: throw new InternalErrorException(e);
585: } finally {
586: try {
587: if (objectInputStream != null) {
588: objectInputStream.close();
589: }
590: if (inputStream != null) {
591: inputStream.close();
592: }
593:
594: } catch (IOException e) {
595: throw new InternalErrorException(e);
596: }
597: }
598: }
599:
600: /**
601: * Reads key field from the result set.
602: *
603: * @param keys the key fields to read
604: * @param resultSet the result set
605: * @return the key fields and values as (<code>String, String</code>). The
606: * first string represents the key field and the second string
607: * represents the value as a <code>String</code>.
608: * @throws SQLException if a database failure occured
609: */
610: private Map readKeys(List keys, ResultSet resultSet)
611: throws SQLException {
612: Map keyMapping = new HashMap();
613: Iterator keysIterator = keys.iterator();
614: while (keysIterator.hasNext()) {
615: String key = (String) keysIterator.next();
616: String value = resultSet.getString(key);
617: keyMapping.put(key, value);
618: }
619:
620: return keyMapping;
621: }
622:
623: }
|