001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/model/repository/sql/storers/RelationalPropertyStorageStrategy.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.PreparedStatement;
044: import java.sql.Statement;
045: import java.sql.ResultSet;
046: import java.sql.SQLException;
047: import java.sql.Connection;
048: import java.util.Map;
049: import java.util.HashMap;
050: import java.util.List;
051: import java.util.ArrayList;
052: import java.util.Iterator;
053: import java.util.Collection;
054: import es.udc.mypersonalizer.kernel.model.properties.Property;
055: import es.udc.mypersonalizer.kernel.model.repository.sql.util.SQLOperations;
056: import es.udc.mypersonalizer.kernel.log.Log;
057: import es.udc.mypersonalizer.kernel.log.LogManager;
058: import es.udc.mypersonalizer.kernel.log.LogNamingConventions;
059: import es.udc.mypersonalizer.kernel.model.annotators.sql.SQLPersistenceTypeAnnotationHelper;
060: import es.udc.mypersonalizer.kernel.model.metainfo.MetaService;
061: import es.udc.mypersonalizer.kernel.util.exceptions.InstanceNotFoundException;
062: import es.udc.mypersonalizer.kernel.util.exceptions.DuplicateInstanceException;
063: import es.udc.mypersonalizer.kernel.util.exceptions.InternalErrorException;
064: import es.udc.mypersonalizer.kernel.util.exceptions.VisitorException;
065: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfig;
066: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfigManager;
067:
068: /**
069: * This class defines a policy to store and retrieve properties on a
070: * database by means of an object to relational mapping. Each property
071: * is kept in one or more database tables, in a way that minimizes the
072: * number of tables necessary to store the information.
073: * <p>
074: * This policy is intended to be passed to "storer" classes.
075: *
076: * @author Fernando Bellas
077: * @author Abel Muinho
078: * @since 1.0
079: */
080:
081: public class RelationalPropertyStorageStrategy implements
082: PropertyStorageStrategy {
083:
084: private static final String GENERATED_IDENTIFIER_COLUMN_NAME;
085:
086: static {
087: String generatedIdentifierColumnName = null;
088: try {
089: DatabaseConventionsConfig config = DatabaseConventionsConfigManager
090: .getConfig();
091: generatedIdentifierColumnName = config
092: .getGeneratedIdentifierColumn();
093: } catch (Exception e) {
094: Log mypersonalizerLog = LogManager
095: .getLog(LogNamingConventions.MYPERSONALIZER);
096: mypersonalizerLog.write(
097: "Could not initialize configuration for "
098: + "RelationalPropertyStorageStrategy", e,
099: RelationalPropertyStorageStrategy.class);
100: }
101: GENERATED_IDENTIFIER_COLUMN_NAME = generatedIdentifierColumnName;
102: }
103:
104: /**
105: * The <code>MetaService</code> for this strategy.
106: */
107: private MetaService metaService = null;
108:
109: /**
110: * The debug mode. This code should be commented out for the final
111: * release !
112: */
113: private boolean debugMode = true;
114:
115: public void setMetainfo(MetaService metainfo) {
116: this .metaService = metainfo;
117: }
118:
119: protected boolean propertyExists(Connection connection, Map key)
120: throws InternalErrorException {
121:
122: // FIXME: Derived from FindMetaPropertyVisitor.visitMetaSimpleProperty
123: PreparedStatement statement = null;
124: ResultSet resultSet = null;
125: boolean exists = false;
126:
127: String query = "SELECT COUNT(*) FROM "
128: + SQLPersistenceTypeAnnotationHelper
129: .getTableNameAnnotation(metaService)
130: + " WHERE " + allAndKeys(key);
131:
132: debug("query: " + query);
133: debug("params: " + key);
134:
135: try {
136: statement = connection.prepareStatement(query);
137: fillKeyValues(statement, key);
138: resultSet = statement.executeQuery();
139:
140: // Compare the value of COUNT(*) with the expected
141: resultSet.next();
142: exists = resultSet.getInt(1) > 0;
143: } catch (SQLException e) {
144: throw new InternalErrorException(e);
145: } finally {
146: SQLOperations.closeStatement(statement);
147: }
148:
149: return exists;
150: }
151:
152: public Property findProperty(Connection connection, Map key)
153: throws InternalErrorException, InstanceNotFoundException {
154:
155: Statement statement = null;
156: ResultSet resultSet = null;
157: debug("findProperty: START ************************************");
158:
159: if (!propertyExists(connection, key)) {
160: throw new InstanceNotFoundException(key, Property.class
161: .getName());
162: }
163:
164: try {
165:
166: Property property = (Property) metaService
167: .getMetaRootProperty().accept(
168: new FindMetaPropertyVisitor(connection,
169: resultSet, key));
170:
171: debug("findProperty: STOP *************************************");
172:
173: return property;
174:
175: } catch (VisitorException e) {
176: throw new InternalErrorException(e);
177: } catch (Exception e) {
178: throw new InternalErrorException(
179: "FATAL ERROR: Exception not expected in"
180: + " RelationalPropertyStorageStrategy while finding "
181: + "Property in table name: "
182: + SQLPersistenceTypeAnnotationHelper
183: .getTableNameAnnotation(metaService)
184: + " with keys: " + key + ". "
185: + e.getMessage());
186: } finally {
187: SQLOperations.closeStatement(statement);
188: }
189: }
190:
191: public Map findPropertiesByRange(Connection connection, List keys,
192: int offset, int size) throws InternalErrorException {
193:
194: /* Create the Map to return. */
195: Map range = new HashMap();
196:
197: /* Get all the keys values in the specified range. */
198: Collection propertyKeys = findPropertyKeysByRange(connection,
199: keys, offset, size);
200:
201: /* Iterate over them. */
202: Iterator propertyKeysIterator = propertyKeys.iterator();
203:
204: while (propertyKeysIterator.hasNext()) {
205:
206: Property property = null;
207:
208: /* Get the values of the keys as a Map from the iterator. */
209: Map keysValues = (Map) propertyKeysIterator.next();
210:
211: /* Fin the Property. */
212: try {
213: property = findProperty(connection, keysValues);
214: } catch (InstanceNotFoundException e) {
215: throw new InternalErrorException(
216: "FATAL ERROR: Exception not expected in"
217: + " RelationalPropertyStorageStrategy while finding"
218: + " Properties by range in table name: "
219: + SQLPersistenceTypeAnnotationHelper
220: .getTableNameAnnotation(metaService)
221: + " with key values: " + keysValues
222: + ". " + e.getMessage());
223: }
224:
225: /* Add the Property to the Map to return. */
226: range.put(keysValues, property);
227: }
228:
229: /* Finally, return the Map. */
230: return range;
231: }
232:
233: public void updateProperty(Connection connection, Map key,
234: Property property) throws InternalErrorException,
235: InstanceNotFoundException {
236:
237: debug("updateProperty: START $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
238:
239: removeProperty(connection, key);
240: try {
241: addProperty(connection, key, property);
242: } catch (DuplicateInstanceException e) {
243: // impossible if remove and add logic is correctly implemented
244: LogManager
245: .getLog(LogNamingConventions.MYPERSONALIZER)
246: .write(
247: "IMPOSSIBLE EXCEPTION: DuplicateInstance after delete.",
248: e, this .getClass());
249: }
250:
251: debug("updateProperty: STOP $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
252: }
253:
254: public void addProperty(Connection connection, Map key,
255: Property property) throws InternalErrorException,
256: DuplicateInstanceException {
257:
258: debug("addProperty: START #####################################");
259:
260: /* No multi-valued: check for existence. */
261: if (!metaService.getMetaRootProperty().isMultiValued()) {
262:
263: if (propertyExists(connection, key)) {
264: throw new DuplicateInstanceException(key, property
265: .getClass().getName());
266: }
267: }
268:
269: try {
270: metaService.getMetaRootProperty().accept(
271: new AddMetaPropertyVisitor(connection, key,
272: property));
273: } catch (Exception e) {
274: throw new InternalErrorException(e);
275: }
276:
277: debug("addProperty: STOP ######################################");
278: }
279:
280: public void removeProperty(Connection connection, Map key)
281: throws InternalErrorException, InstanceNotFoundException {
282:
283: debug("removeProperty: START @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
284:
285: if (!propertyExists(connection, key)) {
286: throw new InstanceNotFoundException(key, Property.class
287: .getName());
288: }
289:
290: try {
291: metaService.getMetaRootProperty().accept(
292: new RemoveMetaPropertyVisitor(connection, key));
293: } catch (Exception e) {
294: throw new InternalErrorException(e);
295: }
296: debug("removeProperty: STOP @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
297: }
298:
299: /**
300: * Returns a <code>String</code> in a format suitable for use with
301: * {@link java.sql.Connection#prepareStatement(String)} where all the key
302: * columns equal a parameter (i.e. "column = ?").
303: *
304: * @param map The map which will converted to a String representing
305: * a SQL contition.
306: * @return a <code>String</code> The formatted <code>String</code>.
307: */
308: // FIXME: Copied from AbstractSQLMetaPropertyVisitor, this needs refactoring.
309: protected String allAndKeys(Map map) {
310: Iterator iterator;
311: Object theKey;
312: String theString;
313:
314: iterator = map.keySet().iterator();
315: theKey = iterator.next();
316: theString = theKey + " = ?";
317: while (iterator.hasNext()) {
318: theKey = iterator.next();
319: theString += " AND " + theKey + " = ?";
320: }
321: return theString;
322: }
323:
324: /**
325: * Returns a list of key names separated by colons.
326: *
327: * @param keys a <code>List</code> of key names.
328: * @return a <code>String</code> with a list of key
329: * names separated by colons.
330: */
331: // FIXME: Also exists in AbstractSQLMetaPropertyVisitor, this needs refactoring.
332: private String allColonKeys(List keys) {
333: Iterator iterator = null;
334: String theKey = null;
335: String theString = "";
336:
337: iterator = keys.iterator();
338: theKey = (String) iterator.next();
339: theString += theKey;
340: while (iterator.hasNext()) {
341: theKey = (String) iterator.next();
342: theString += ", " + theKey;
343: }
344: return theString;
345: }
346:
347: /**
348: * Adds the key values in <code>key</code> to the <code>PreparedStatement</code>,
349: * starting at the first parameter.
350: * @param statement <code>Statement</code> whose parameters are to be
351: * assigned a value.
352: * @param key Key values to fill.
353: * @throws VisitorException if the parameters can't be assigned a value.
354: */
355: // FIXME: Adapted from AbstractSQLMetaPropertyVisitor, this needs refactoring.
356: protected void fillKeyValues(PreparedStatement statement, Map key)
357: throws SQLException {
358:
359: // let the JDBC driver handle key-field types.
360: Iterator keyValuesIterator = key.values().iterator();
361: int paramNumber = 1;
362: while (keyValuesIterator.hasNext()) {
363: statement
364: .setObject(paramNumber++, keyValuesIterator.next());
365: }
366: }
367:
368: /**
369: * Extracts a range of property keys by using the key fields.
370: *
371: * @param connection the connection to the database
372: * @param keys the key columns as a list of <code>String</code>s
373: * @param offset the number of initial keys to be skipped. This parameter
374: * must be greather or equals than 0
375: * @param size the number of keys to be extracted
376: * @return the key range as a collection of maps. If the collection size
377: * is less than the parameter "size" that means that there are no
378: * more keys to read.
379: * @throws InternalErrorException if a failure is detected.
380: */
381: private Collection findPropertyKeysByRange(Connection connection,
382: List keys, int offset, int size)
383: throws InternalErrorException {
384:
385: debug("findPropertyKeysByRange: START ************************************");
386: /* Create the List to return. */
387: List keysRange = new ArrayList();
388:
389: /* Construct a list of keys to retrieve. */
390: String allColonKeys = allColonKeys(keys);
391:
392: Statement statement = null;
393: ResultSet resultSet = null;
394: String query = "SELECT "
395: + allColonKeys
396: + " FROM "
397: + SQLPersistenceTypeAnnotationHelper
398: .getTableNameAnnotation(metaService)
399: + " ORDER BY " + allColonKeys;
400:
401: if (metaService.getMetaRootProperty().isMultiValued()) {
402: query += ", " + GENERATED_IDENTIFIER_COLUMN_NAME;
403: }
404:
405: debug("query: " + query);
406:
407: try {
408: statement = connection.createStatement(
409: ResultSet.TYPE_SCROLL_INSENSITIVE,
410: ResultSet.CONCUR_READ_ONLY);
411:
412: resultSet = statement.executeQuery(query);
413:
414: /* Set the position to the offset. */
415: if (offset != 0) {
416: resultSet.absolute(offset);
417: }
418:
419: while ((size-- > 0) && resultSet.next()) {
420: /* Create the Map with the key values to return. */
421: Map keyValues = new HashMap();
422: /* Iterate over the key names. */
423: Iterator iterator = keys.iterator();
424: while (iterator.hasNext()) {
425: /* Get the key name. */
426: String keyName = (String) iterator.next();
427: /* Retrieve the value from the resultSet. */
428: String keyValue = resultSet.getString(keyName);
429: /* Insert into the Map. */
430: keyValues.put(keyName, keyValue);
431: }
432:
433: /* Insert the resulting map (key,value) into the range map. */
434: keysRange.add(keyValues);
435: }
436:
437: debug("findPropertyKeysByRange: STOP *************************************");
438:
439: return keysRange;
440:
441: } catch (Exception e) {
442: throw new InternalErrorException(e);
443: } finally {
444: SQLOperations.closeStatement(statement);
445: }
446:
447: }
448:
449: /**
450: * Echoes a message to the MyPersonalizer log if debugMode is enabled.
451: *
452: * @param message the message to be displayed.
453: */
454: private void debug(String message) {
455:
456: if (debugMode) {
457: Log mypersonalizerLog = LogManager
458: .getLog(LogNamingConventions.MYPERSONALIZER);
459: mypersonalizerLog.write(message, null,
460: RelationalPropertyStorageStrategy.class);
461: }
462:
463: }
464:
465: }
|