001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/model/repository/sql/storers/AddMetaPropertyVisitor.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.SQLException;
045: import java.sql.Connection;
046: import java.sql.Types;
047: import java.util.ArrayList;
048: import java.util.Collection;
049: import java.util.Map;
050: import java.util.HashMap;
051: import java.util.Iterator;
052: import java.util.Arrays;
053:
054: import es.udc.mypersonalizer.kernel.log.Log;
055: import es.udc.mypersonalizer.kernel.log.LogManager;
056: import es.udc.mypersonalizer.kernel.log.LogNamingConventions;
057: import es.udc.mypersonalizer.kernel.model.properties.Property;
058: import es.udc.mypersonalizer.kernel.model.properties.PropertyStructure;
059: import es.udc.mypersonalizer.kernel.util.exceptions.InternalErrorException;
060: import es.udc.mypersonalizer.kernel.util.exceptions.VisitorException;
061: import es.udc.mypersonalizer.kernel.model.annotators.sql.SQLPersistenceTypeAnnotationHelper;
062: import es.udc.mypersonalizer.kernel.model.metainfo.MetaProperty;
063: import es.udc.mypersonalizer.kernel.model.metainfo.MetaSimpleProperty;
064: import es.udc.mypersonalizer.kernel.model.metainfo.MetaCompoundProperty;
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 is a concrete Visitor in the "Visitor" pattern.
070: * It defines a way to <b>add</b> a <code>Property</code>
071: * in a database by means of a object to relational mapping.
072: * Each property is kept in one or more database tables.
073: * <p>
074: * It is responsability of the "Visitor" implementations to
075: * make the traverse through the hierarchical structure.
076: *
077: * @author Abel Muinho
078: * @author Fernando Bellas
079: * @since 1.0
080: */
081:
082: public class AddMetaPropertyVisitor extends
083: AbstractSQLMetaPropertyVisitor {
084:
085: private static final String PROPERTY_IDENTIFIER_COLUMN_NAME;
086: private static final String GENERATED_IDENTIFIER_SEQUENCE_INFIX;
087: private static final String GENERATED_IDENTIFIER_COLUMN_NAME;
088:
089: static {
090: String generatedIdentifierColumnName = null;
091: String generatedIdenfierSequenceInfix = null;
092: String propertyIdentifierColumnName = null;
093: try {
094: DatabaseConventionsConfig config = DatabaseConventionsConfigManager
095: .getConfig();
096: generatedIdentifierColumnName = config
097: .getGeneratedIdentifierColumn();
098: generatedIdenfierSequenceInfix = config
099: .getGeneratedIdentifierSequenceInfix();
100: propertyIdentifierColumnName = config
101: .getPropertyIdentifierColumn();
102: } catch (Exception e) {
103: Log mypersonalizerLog = LogManager
104: .getLog(LogNamingConventions.MYPERSONALIZER);
105: mypersonalizerLog.write(
106: "Could not initialize configuration for "
107: + "AddMetaPropertyVisitor", e,
108: AddMetaPropertyVisitor.class);
109: }
110: GENERATED_IDENTIFIER_COLUMN_NAME = generatedIdentifierColumnName;
111: GENERATED_IDENTIFIER_SEQUENCE_INFIX = generatedIdenfierSequenceInfix;
112: PROPERTY_IDENTIFIER_COLUMN_NAME = propertyIdentifierColumnName;
113: }
114:
115: /**
116: * The <code>Property</code> for this visitor.
117: */
118: private Property property;
119:
120: /**
121: * The last generated unique key for visited data.
122: */
123: private Long lastGeneratedKey = null;
124:
125: /**
126: * The debug mode. This code should be commented out for the final
127: * release !
128: */
129: private boolean debugMode = true;
130:
131: /**
132: * Creates an instance of this class.
133: *
134: * @param connection a connection to the database
135: * @param key the map that contains pairs of key fields and their
136: * corresponding values.
137: * @param property the property to be added
138: */
139: public AddMetaPropertyVisitor(Connection connection, Map key,
140: Property property) {
141:
142: super (connection, key);
143: this .property = property;
144: }
145:
146: /**
147: * Visit operation for a <code>MetaSimpleProperty</code>.
148: *
149: * @param metaProperty the <code>MetaSimpleProperty</code>
150: * object over which the visitor can operate.
151: *
152: * @return an <code>Object</code> of needful for the caller.
153: * In this case it's <code>null</code>
154: * @throws VisitorException if an internal error occurs while
155: * processing the visited object
156: */
157: public Object visitMetaSimpleProperty(
158: MetaSimpleProperty metaProperty) throws VisitorException {
159:
160: PreparedStatement statement = null;
161: String query = null;
162: Iterator iterator = Arrays.asList(property.getValuesAsString())
163: .iterator();
164: /* Generated table names matches the column name. */
165:
166: /* If multi-valued ... */
167: if (metaProperty.isMultiValued()) {
168: query = getInsertQuery(metaProperty);
169: statement = createStatement(query);
170:
171: /* For each value, insert a row in the corresponding table.
172: * If there's no value, insert null anyway. */
173: do {
174: Object currentValue;
175: if (iterator.hasNext()) {
176: currentValue = iterator.next();
177: } else {
178: currentValue = null;
179: }
180: debug("query: " + query);
181: fillColumnValues(statement, metaProperty, currentValue);
182: executeUpdate(statement);
183: } while (iterator.hasNext());
184: } else { // Otherwise, if single-valued...
185: /*
186: * ... assures that there is a value, and updates
187: * the corresponding table (the parent one).
188: */
189: if (iterator.hasNext()) {
190: query = getUpdateQuery(metaProperty);
191: debug("query: " + query);
192: statement = createStatement(query);
193: int param = fillColumnValues(statement, metaProperty,
194: iterator.next());
195: fillKeyValues(statement, param + 1);
196: executeUpdate(statement);
197: }
198: }
199:
200: closeStatement(statement);
201:
202: return null;
203: }
204:
205: /**
206: * Visit operation for a <code>MetaCompoundProperty</code>.
207: *
208: * @param metaProperty the <code>MetaCompoundProperty</code>
209: * object over which the visitor can operate.
210: *
211: * @return an <code>Object</code> of needful for the caller.
212: * In this case it's <code>null</code>
213: * @throws VisitorException if an internal error occurs while
214: * processing the visited object
215: */
216: public Object visitMetaCompoundProperty(
217: MetaCompoundProperty metaProperty) throws VisitorException {
218:
219: PreparedStatement statement = null;
220: String tableName = getTableName(metaProperty);
221:
222: /* Get a new PropertyStructure */
223: PropertyStructure[] values = (PropertyStructure[]) property
224: .getValuesAsObject();
225:
226: /* Iterate over them */
227: Iterator iterator = Arrays.asList(values).iterator();
228:
229: do {
230: PropertyStructure currentValue;
231: if (iterator.hasNext()) {
232: currentValue = (PropertyStructure) iterator.next();
233: } else {
234: currentValue = null;
235: }
236: /* Insert a database row with the corresponding keys,
237: * even if there's no values. */
238: String query = getInsertQuery(metaProperty);
239:
240: debug("query: " + query);
241: statement = createStatement(query);
242: fillColumnValues(statement, metaProperty, values);
243: executeUpdate(statement);
244:
245: // Continue only if the property has any values
246: if (currentValue != null) {
247: /* For each sub-property, add call accept */
248: Map propertyStructureMap = currentValue.getAsMap();
249: Iterator metaPropertyIterator = metaProperty
250: .getMetaProperties();
251:
252: /*
253: * Iterate over the meta-property. We could iterate over
254: * the property structure, but the algorithm could became
255: * non generical.
256: */
257: while (metaPropertyIterator.hasNext()) {
258: MetaProperty aMetaProperty = (MetaProperty) metaPropertyIterator
259: .next();
260: Property subProperty = (Property) propertyStructureMap
261: .get(aMetaProperty.getSimpleName());
262:
263: /*
264: * If the property structure map is not empty then
265: * process its sub-properties.
266: */
267: if (subProperty != null) {
268: Map newKey = new HashMap();
269:
270: /* Establish the new keys for the sub-property */
271: if (metaProperty.isMultiValued()) {
272: if (aMetaProperty.isMultiValued()) {
273: newKey
274: .put(
275: PROPERTY_IDENTIFIER_COLUMN_NAME,
276: getLastGeneratedKey());
277: } else {
278: newKey
279: .put(
280: GENERATED_IDENTIFIER_COLUMN_NAME,
281: getLastGeneratedKey());
282: }
283: } else {
284: newKey = getKey();
285: }
286:
287: /* And do the call */
288: aMetaProperty
289: .accept(new AddMetaPropertyVisitor(
290: getConnection(), newKey,
291: subProperty));
292: }
293: }
294: }
295: } while (iterator.hasNext());
296:
297: closeStatement(statement);
298:
299: return null;
300: }
301:
302: /**
303: * Obtains a new unique-key for an instance of the given
304: * MetaProperty.
305: *
306: * @param metaProperty MetaProperty.
307: * @return String The generated key
308: * @throws VisitorException if an error occurs while generating the key.
309: */
310: protected Long getGeneratedKey(MetaProperty metaProperty)
311: throws VisitorException {
312: String tableName = getTableName(metaProperty);
313:
314: try {
315: EntityIdentifierGeneratorSingleton generator = EntityIdentifierGeneratorSingleton
316: .getInstance();
317: String idSequenceName = tableName
318: + GENERATED_IDENTIFIER_SEQUENCE_INFIX;
319:
320: lastGeneratedKey = generator.nextIdentifier(
321: getConnection(), idSequenceName);
322: } catch (InternalErrorException e) {
323: throw new VisitorException(
324: "FATAL ERROR: getting next identifier"
325: + " from sequence of table name: "
326: + tableName
327: + " for a multi-valued Property: "
328: + property.getSimpleName(), e);
329: }
330: return lastGeneratedKey;
331: }
332:
333: /**
334: * Returns the last key generated.
335: * @return String a String representing the last generated key.
336: */
337: protected Long getLastGeneratedKey() {
338: return lastGeneratedKey;
339: }
340:
341: /**
342: * Returns the keys of the Map.
343: * @return a <code>Collection</code> with the key's names.
344: */
345: private Collection allKeys() {
346: return new ArrayList(getKey().keySet());
347: }
348:
349: /**
350: * Returns the names of all the columns needed to store a Property defined
351: * by the given MetaProperty in a RDBMS.
352: * @param metaProperty The Metaproperty describing the structure.
353: * @return a <code>Collection</code> with all column names.
354: */
355: protected Collection allColumns(
356: final MetaSimpleProperty metaProperty) {
357: ArrayList result = new ArrayList();
358:
359: if (metaProperty.isMultiValued()) {
360: result.addAll(allKeys());
361: result.add(GENERATED_IDENTIFIER_COLUMN_NAME);
362: }
363: result.add(SQLPersistenceTypeAnnotationHelper
364: .getColumnNameAnnotation(metaProperty));
365:
366: return result;
367: }
368:
369: /**
370: * Returns the names of all the columns needed to store a Property defined
371: * by the given MetaProperty in a RDBMS.
372: * @param metaProperty The Metaproperty describing the structure.
373: * @return a <code>Collection</code> with all column names.
374: */
375: protected Collection allColumns(
376: final MetaCompoundProperty metaProperty) {
377: ArrayList result = new ArrayList(allKeys());
378:
379: if (metaProperty.isMultiValued()) {
380: result.add(GENERATED_IDENTIFIER_COLUMN_NAME);
381: }
382:
383: return result;
384: }
385:
386: /**
387: * Returns the values of the keys of the Map.
388: * @return a <code>Collection</code> with the key's values.
389: */
390: protected Collection allKeyValues() {
391: Map key = getKey();
392:
393: return new ArrayList(key.values());
394: }
395:
396: /**
397: * Returns the values of all the columns needed to store a Property defined
398: * by the given MetaProperty in a RDBMS.
399: * @param metaProperty The Metaproperty describing the structure.
400: * @param value An object representing the property's value.
401: * @return a <code>Collection</code> with all the column values.
402: * @throws VisitorException if a unique key needs to be generated and an
403: * error is reported.
404: */
405: protected Collection allColumnValues(
406: MetaSimpleProperty metaProperty, Object value)
407: throws VisitorException {
408:
409: ArrayList result = new ArrayList();
410:
411: if (metaProperty.isMultiValued()) {
412: result.addAll(allKeyValues());
413: /* We need to identify those properties with 0 values.
414: * We accomplish it by setting a null generated id. */
415: if (value != null) {
416: result.add(getGeneratedKey(metaProperty));
417: } else {
418: result.add(null);
419: }
420: }
421: result.add(value);
422: return result;
423: }
424:
425: /**
426: * Returns the values of all the columns needed to store a Property defined
427: * by the given MetaProperty in a RDBMS.
428: * @param metaProperty The Metaproperty describing the structure.
429: * @param value An object representing the property's value.
430: * @return a <code>Collection</code> with all the key fields' values.
431: * @throws VisitorException if a unique key needs to be generated and an
432: * error is reported.
433: */
434: protected Collection allColumnValues(
435: MetaCompoundProperty metaProperty, Object value)
436: throws VisitorException {
437:
438: Collection result = new ArrayList(allKeyValues());
439:
440: if (metaProperty.isMultiValued()) {
441: PropertyStructure[] typedValues = (PropertyStructure[]) value;
442: /* We need to identify those properties with 0 values.
443: * We accomplish it by setting a null generated id. */
444: if (typedValues.length > 0) {
445: result.add(getGeneratedKey(metaProperty));
446: } else {
447: result.add(null);
448: }
449: }
450: return result;
451: }
452:
453: /**
454: * Echoes a message to the MyPersonalizer log if debugMode is enabled.
455: *
456: * @param message the message to be displayed.
457: */
458: private void debug(String message) {
459:
460: if (debugMode) {
461: Log mypersonalizerLog = LogManager
462: .getLog(LogNamingConventions.MYPERSONALIZER);
463: mypersonalizerLog.write(message, null,
464: AddMetaPropertyVisitor.class);
465: }
466:
467: }
468:
469: /**
470: * Returns the current property being accessed.
471: * @return The <code>Property</code> representing the current data.
472: */
473: protected Property getProperty() {
474: return property;
475: }
476:
477: /**
478: * Obtains the parametrized SQL query for inserting a row.
479: * @param metaProperty Metainformation needed to build the query.
480: * @return A parametrized SQL INSERT query.
481: */
482: protected String getInsertQuery(MetaProperty metaProperty) {
483: Collection columns = (metaProperty instanceof MetaSimpleProperty) ? allColumns((MetaSimpleProperty) metaProperty)
484: : allColumns((MetaCompoundProperty) metaProperty);
485: Iterator it = columns.iterator();
486: StringBuffer columnsString = new StringBuffer(
487: columns.size() * 15);
488: StringBuffer valuesString = new StringBuffer(columns.size() * 3);
489:
490: // Always has at least one column (value if SimpleMetaProperty,
491: // the key if CompoundMetaProperty), so it is safe to access the
492: // first element without checks.
493: columnsString.append(it.next());
494: valuesString.append("?");
495: while (it.hasNext()) {
496: columnsString.append(", ").append(it.next());
497: valuesString.append(", ?");
498: }
499:
500: return "INSERT INTO " + getTableName(metaProperty) + " ("
501: + columnsString + ") VALUES (" + valuesString + ")";
502: }
503:
504: /**
505: * Obtains the parametrized SQL query for Updating a row.
506: * @param metaProperty Metainformation needed to build the query.
507: * @return A parametrized SQL UPDATE query.
508: */
509: protected String getUpdateQuery(MetaProperty metaProperty) {
510: Collection columns = (metaProperty instanceof MetaSimpleProperty) ? allColumns((MetaSimpleProperty) metaProperty)
511: : allColumns((MetaCompoundProperty) metaProperty);
512: Iterator it = columns.iterator();
513: // assume an average of 15 characters per column name
514: // plus " = ?, " -> 5 extra chars
515: // for a total average of 20 characters per column.
516: // If this is accurate, no resizing of the StringBuffer will be done.
517: StringBuffer nameValues = new StringBuffer(columns.size() * 20);
518:
519: // Allways has at least one column (value if SimpleMetaProperty,
520: // the key if CompoundMetaProperty), so it is safe to access the
521: // first element without checks.
522: nameValues.append(it.next()).append(" = ?");
523: while (it.hasNext()) {
524: nameValues.append(", ").append(it.next()).append(" = ?");
525: }
526:
527: return "UPDATE "
528: + SQLPersistenceTypeAnnotationHelper
529: .getTableNameAnnotation(metaProperty) + " SET "
530: + nameValues + " WHERE " + allAndKeys();
531: }
532:
533: /**
534: * Adds the column values to the <code>PreparedStatement</code> starting
535: * with the first parameter.
536: * @param statement <code>Statement</code> whose parameters are to be
537: * assigned a value.
538: * @param metaProperty Metainformation needed to fill the values.
539: * @param values The object containing the column values.
540: * @return the index of the last parameter set.
541: * @throws VisitorException if the parameters can't be assigned a value.
542: */
543: protected int fillColumnValues(PreparedStatement statement,
544: MetaProperty metaProperty, Object values)
545: throws VisitorException {
546:
547: Collection theValues = (metaProperty instanceof MetaSimpleProperty) ? allColumnValues(
548: (MetaSimpleProperty) metaProperty, values)
549: : allColumnValues((MetaCompoundProperty) metaProperty,
550: values);
551: int param = 0;
552: Iterator it = theValues.iterator();
553: Object currentValue = null; // exists just for error reporting
554:
555: try {
556: while (it.hasNext()) {
557: currentValue = it.next();
558: debug("param " + (param + 1) + " = " + currentValue);
559: // Some databases (like Oracle) don't handle null values
560: // using setObject. Besides it seems more in the spirit
561: // of the Javadoc documentation for setObject.
562: if (currentValue != null) {
563: statement.setObject(++param, currentValue);
564: } else {
565: statement.setNull(++param, Types.BIGINT);
566: }
567: }
568: return param;
569: } catch (SQLException e) {
570: throw new VisitorException(
571: "ERROR: Can't set parameters in PreparedStatement (param="
572: + param + ", value=" + currentValue);
573: }
574: }
575: }
|