001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/model/annotators/sql/SQLPersistenceTypeAnnotator.java,v 1.1.1.1 2004/03/25 12:08:37 fbellas Exp $
003: * $Revision: 1.1.1.1 $
004: * $Date: 2004/03/25 12:08:37 $
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 Coruņa
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 Coruņa 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.annotators.sql;
042:
043: import java.util.ArrayList;
044: import java.util.List;
045: import java.util.Map;
046: import java.util.Random;
047:
048: import es.udc.mypersonalizer.kernel.conventions.ServiceConventions;
049: import es.udc.mypersonalizer.kernel.log.Log;
050: import es.udc.mypersonalizer.kernel.log.LogManager;
051: import es.udc.mypersonalizer.kernel.log.LogNamingConventions;
052: import es.udc.mypersonalizer.kernel.model.annotators.AbstractAnnotatorVisitor;
053: import es.udc.mypersonalizer.kernel.model.annotators.AnnotationHelper;
054: import es.udc.mypersonalizer.kernel.model.annotators.Annotations;
055: import es.udc.mypersonalizer.kernel.model.annotators.AnnotatorVisitor;
056: import es.udc.mypersonalizer.kernel.model.annotators.PersistenceTypeAnnotationHelper;
057: import es.udc.mypersonalizer.kernel.model.metainfo.MetaCompoundProperty;
058: import es.udc.mypersonalizer.kernel.model.metainfo.MetaProperty;
059: import es.udc.mypersonalizer.kernel.model.metainfo.MetaService;
060: import es.udc.mypersonalizer.kernel.model.metainfo.MetaServiceRegistrySingleton;
061: import es.udc.mypersonalizer.kernel.model.metainfo.MetaSimpleProperty;
062: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfig;
063: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseConventionsConfigManager;
064: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseSpecificsConfig;
065: import es.udc.mypersonalizer.kernel.model.repository.sql.config.DatabaseSpecificsConfigManager;
066: import es.udc.mypersonalizer.kernel.util.exceptions.InternalErrorException;
067: import es.udc.mypersonalizer.kernel.util.exceptions.VisitorException;
068:
069: /**
070: * Annotator class which provides default annotations for the SQL persistence
071: * type. These defaults can be replaced by externally-provided annotations.
072: * <p>
073: * The annotations set are:
074: * <dl>
075: * <dt>tableName</dt>
076: * <dd>on <code>MetaService</code>s, with it's service identifier as the
077: * default value, and on every <code>MetaProperty</code> using a name
078: * generated by appending the column name of multivalued or compount
079: * MetaProperties to the parent element's table name. Simple univalued
080: * MetaProperties inherit the table name unchanged.</dd>
081: * <dt>columName</dt>
082: * <dd>on</code> SimpleMetaProperty</code> objects (both simple and
083: * compound). It defaults to the property's simple name, except for the root
084: * property, which <b>must </b> use the same name as the service's table.</dd>
085: * <dt>dbType</dt>
086: * <dd>on <code>SimpleMetaProperty</code> objects only. The default value is
087: * derived from the java type and the database-specific configuration.</dd>
088: * </dl>
089: * Additionally, the following annotations are generated, regardless of the
090: * existence of user-provided values (i.e. this values will overwrite user
091: * provided data for the same annotations):
092: * <dl>
093: * <dt>tableName</dt>
094: * <dd>on <code>MetaProperty</code> objects. The value is set using a name
095: * generated by appending the column name of multivalued or compount
096: * MetaProperties to the parent element's table name. Simple univalued
097: * MetaProperties inherit the table name unchanged.</dd>
098: * <dt>persistenceType</dt>
099: * <dd>on <code>MetaProperties</code> (both simple and compound), copied
100: * from the <code>MetaService</code> attribute. User provided values will be
101: * replaced (generated property).</dd>
102: * <dt>idColumn</dt>
103: * <dd>on <code>MetaProperties</code>, multivalued and/or compound, with
104: * the name of the column storing the unique identifier of the persisted
105: * property.</dd>
106: * <dt>fkColumn</dt>
107: * <dd>on <code>MetaProperties</code>, multivalued and/or compound, with
108: * the name of the column storing the foreign key of the persisted property.
109: * </dd>
110: * </dl>
111: * Other annotations are passed as-is.
112: *
113: * @author Abel Muinho
114: * @since 1.0
115: */
116: public class SQLPersistenceTypeAnnotator extends
117: AbstractAnnotatorVisitor {
118: private final static Log log = LogManager
119: .getLog(LogNamingConventions.MYPERSONALIZER);
120:
121: /** Cached configuration data. */
122: private static int maxTableNameLength = -1;
123:
124: /** Cached configuration data. */
125: private static Map javaToSQLMapping = null;
126:
127: /** Name of the column holding the login name identifier. */
128: private static String LOGIN_ID_COLUMN;
129:
130: /**
131: * Name of the column holding the generated identifier for multivalued
132: * properties.
133: */
134: private static String GENERATED_ID_COLUMN;
135:
136: /** Name of the column holding the property identifier. */
137: private static String PROPERTY_ID_COLUMN;
138:
139: /** Metaservice being visited. */
140: private MetaService metaService = null;
141:
142: /** String with the table name for the root metaProperty. */
143: private String rootTable = null;
144:
145: /** List of generated table names. The service's table is at index 0. */
146: private List tableNames = new ArrayList();
147:
148: /** Variable information kept by the visitor. */
149: private State visitState = new State();
150:
151: /* MetaProperty.Visitor interface. */
152: // @see
153: // es.udc.mypersonalizer.kernel.model.annotators.AbstractAnnotatorVisitor#visitMetaCompoundProperty(es.udc.mypersonalizer.kernel.model.metainfo.MetaCompoundProperty)
154: public Object visitMetaCompoundProperty(
155: MetaCompoundProperty metaProperty) throws VisitorException {
156:
157: /* Backup visit state before descending this property. */
158: State oldState = visitState.copy();
159:
160: /* Compound metaproperties always require a table, increment. */
161: visitState.numberOfTables++;
162: super .visitMetaCompoundProperty(metaProperty);
163:
164: /* Restore visit state when ascending. */
165: visitState = oldState;
166:
167: return null;
168: }
169:
170: // @see
171: // es.udc.mypersonalizer.kernel.model.annotators.AbstractAnnotatorVisitor#visitMetaSimpleProperty(es.udc.mypersonalizer.kernel.model.metainfo.MetaSimpleProperty)
172: public Object visitMetaSimpleProperty(
173: MetaSimpleProperty metaProperty) throws VisitorException {
174:
175: /* Backup visit state before descending this property. */
176: State oldState = visitState.copy();
177:
178: /* Multivalued metasimpleproperties require a table. */
179: if (metaProperty.isMultiValued()) {
180: visitState.numberOfTables++;
181: }
182: super .visitMetaSimpleProperty(metaProperty);
183:
184: /* Restore visit state when ascending. */
185: visitState = oldState;
186:
187: return null;
188: }
189:
190: /* Annotator interface. */
191:
192: public AnnotatorVisitor getInstance() throws InternalErrorException {
193: /* Does configuration data need to be fetched? */
194: if (maxTableNameLength == -1) {
195: DatabaseSpecificsConfig config = DatabaseSpecificsConfigManager
196: .getConfig();
197: maxTableNameLength = config.getMaxIdentifierLength();
198: javaToSQLMapping = config.getDefaultJavaToSQLMapping();
199: DatabaseConventionsConfig conventions = DatabaseConventionsConfigManager
200: .getConfig();
201: PROPERTY_ID_COLUMN = conventions
202: .getPropertyIdentifierColumn();
203: GENERATED_ID_COLUMN = conventions
204: .getGeneratedIdentifierColumn();
205: LOGIN_ID_COLUMN = conventions.getLoginColumn();
206: }
207: return new SQLPersistenceTypeAnnotator();
208: }
209:
210: public void annotate(MetaService metaService,
211: Annotations annotations) {
212: if (this .metaService != null) {
213: throw new IllegalStateException(
214: "Already visited a MetaService.");
215: } else {
216: this .metaService = metaService;
217: }
218:
219: if (metaService
220: .getServiceIdentifier()
221: .equals(
222: ServiceConventions.USER_REGISTRATION_INFORMATION_SERVICE_IDENTIFIER)) {
223: visitState.keyColumn = LOGIN_ID_COLUMN;
224: } else {
225: visitState.keyColumn = PROPERTY_ID_COLUMN;
226: }
227:
228: AnnotationHelper
229: .setIfEmpty(
230: annotations,
231: SQLPersistenceTypeAnnotationHelper.TABLE_NAME_ANNOTATION,
232: metaService.getServiceIdentifier());
233: metaService.setAnnotations(annotations);
234:
235: /* Initialize the table name property. */
236: rootTable = SQLPersistenceTypeAnnotationHelper
237: .getTableNameAnnotation(metaService);
238: tableNames.add(rootTable);
239: }
240:
241: public void annotate(MetaSimpleProperty metaSimpleProperty,
242: Annotations annotations) throws InternalErrorException {
243:
244: /* Annotate common properties. */
245: annotateMetaProperty(metaSimpleProperty, annotations);
246:
247: /* Annotate database type. */
248: String dbType = (String) javaToSQLMapping
249: .get(metaSimpleProperty.getJavaType().getName());
250: if (dbType != null) {
251: AnnotationHelper
252: .setIfEmpty(
253: annotations,
254: SQLPersistenceTypeAnnotationHelper.DB_TYPE_ANNOTATION,
255: dbType);
256: } else {
257: throw new InternalErrorException(metaSimpleProperty
258: .getJavaType().getName()
259: + " is not supported by the running database.");
260: }
261:
262: /* Annotate column name. */
263: AnnotationHelper
264: .setIfEmpty(
265: annotations,
266: SQLPersistenceTypeAnnotationHelper.COLUMN_NAME_ANNOTATION,
267: metaSimpleProperty.getSimpleName());
268:
269: /* Commit annotations to the metaproperty */
270: metaSimpleProperty.setAnnotations(annotations);
271: }
272:
273: public void annotate(MetaCompoundProperty metaCompoundProperty,
274: Annotations annotations) {
275:
276: if (annotations
277: .get(SQLPersistenceTypeAnnotationHelper.COLUMN_NAME_ANNOTATION) != null) {
278: log.write("MetaService: "
279: + metaService.getServiceIdentifier()
280: + " \t MetaProperty: "
281: + metaCompoundProperty.getSimpleName()
282: + "\ncolumnName annotation is ignored for"
283: + " compound metaproperties.", null,
284: SQLPersistenceTypeAnnotator.class);
285: }
286: annotateMetaProperty(metaCompoundProperty, annotations);
287: metaCompoundProperty.setAnnotations(annotations);
288: }
289:
290: /**
291: * Adds annotations common to both Simple and Compound <code>MetaProperties</code>.
292: *
293: * @param metaProperty
294: * the <code>MetaProperty</code> to be annotated.
295: * @param annotations
296: * the external annotations to use.
297: */
298: private void annotateMetaProperty(MetaProperty metaProperty,
299: Annotations annotations) {
300:
301: if (metaService == null) {
302: throw new IllegalStateException(
303: "Trying to annotate a metaproperty"
304: + " before annotating its metaservice.");
305: }
306:
307: /* Override any previous value, this is a generated annotation. */
308: annotations
309: .set(
310: PersistenceTypeAnnotationHelper.PERSISTENCE_TYPE_ANNOTATION,
311: metaService.getPersistenceType());
312:
313: visitState.fkColumn = visitState.keyColumn;
314: if (metaProperty.isMultiValued()) {
315: visitState.keyColumn = GENERATED_ID_COLUMN;
316: } else if (metaProperty instanceof MetaCompoundProperty
317: && visitState.keyColumn == GENERATED_ID_COLUMN) {
318: visitState.keyColumn = PROPERTY_ID_COLUMN;
319: }
320: annotations
321: .set(
322: SQLPersistenceTypeAnnotationHelper.ID_COLUMN_ANNOTATION,
323: visitState.keyColumn);
324: annotations
325: .set(
326: SQLPersistenceTypeAnnotationHelper.FK_COLUMN_ANNOTATION,
327: visitState.fkColumn);
328: if (metaProperty == metaService.getMetaRootProperty()) {
329: /*
330: * It is required that the table name is the same as the
331: * metaservice's table name (for the root metaproperty).
332: */
333: if (annotations
334: .get(SQLPersistenceTypeAnnotationHelper.TABLE_NAME_ANNOTATION) != null) {
335: log.write("MetaService: "
336: + metaService.getServiceIdentifier()
337: + " \t MetaProperty: "
338: + metaProperty.getSimpleName()
339: + "\ntableName annotation is not allowed for "
340: + "the root metaproperty. It will be ignored.",
341: null, SQLPersistenceTypeAnnotator.class);
342: }
343: annotations
344: .set(
345: SQLPersistenceTypeAnnotationHelper.TABLE_NAME_ANNOTATION,
346: SQLPersistenceTypeAnnotationHelper
347: .getTableNameAnnotation(metaService));
348: } else {
349: String tableName = generateTableName(metaProperty);
350: AnnotationHelper
351: .setIfEmpty(
352: annotations,
353: SQLPersistenceTypeAnnotationHelper.TABLE_NAME_ANNOTATION,
354: tableName);
355: }
356: }
357:
358: /**
359: * Returns the generated name for the given metaproperty.
360: * <p>
361: * The name will be generated by appending the service name and the table
362: * counter for the service. Additionally, the metaproperty name will be
363: * appended (up to the maximum identifier length for the database) in order
364: * to make the relation between metaproperties and table easier to
365: * recognize.
366: *
367: * @param mp
368: * the metaproperty whose table name is to be returned.
369: * @return the table name for the given metaproperty.
370: */
371: private String generateTableName(MetaProperty mp) {
372: String result;
373: if (visitState.numberOfTables == 0) {
374: /* Use metaservice's table. */
375: result = rootTable;
376: } else if (!needsTable(mp)) {
377: /* Use previously created table. */
378: result = (String) tableNames.get(visitState.numberOfTables);
379: } else {
380: StringBuffer buffer = new StringBuffer(maxTableNameLength);
381: buffer.append(rootTable).append(visitState.numberOfTables);
382: int remainingCapacity = maxTableNameLength
383: - buffer.length();
384: if (remainingCapacity < 0) {
385: int random = new Random().nextInt();
386: String randomTableName = "tbl"
387: + Integer.toString(random, Character.MAX_RADIX);
388: log
389: .write(
390: "Generated table name is "
391: + (-remainingCapacity)
392: + " characters too long. Using a random table name ("
393: + randomTableName
394: + ") and hopping for the best.",
395: null, SQLPersistenceTypeAnnotator.class);
396: result = randomTableName;
397: } else {
398: String propertyName = mp.getSimpleName();
399: int namePartLength = Math.min(remainingCapacity,
400: propertyName.length());
401: buffer
402: .append(propertyName.substring(0,
403: namePartLength));
404: result = buffer.toString();
405: }
406: }
407: if (visitState.numberOfTables < tableNames.size()) {
408: tableNames.set(visitState.numberOfTables, result);
409: } else {
410: tableNames.add(result);
411: }
412: return result;
413: }
414:
415: /**
416: * Determines if a property needs a new table to be stored.
417: *
418: * @param mp
419: * the metaproperty.
420: * @return <code>true</code> if the metaproperty needs a new table,
421: * <code>false</code> otherwise.
422: */
423: private boolean needsTable(MetaProperty mp) {
424: return !(mp instanceof MetaSimpleProperty && !mp
425: .isMultiValued());
426: }
427:
428: /**
429: * Private class holding the <b>internal </b> visit state.
430: *
431: * @author Abel Muinho
432: * @since 1.0
433: */
434: private class State {
435: /** Column name for the foreign key for the most recent table. */
436: public String fkColumn = null;
437:
438: /** Unique Key Column name for the most-recent table. */
439: public String keyColumn = null;
440:
441: /**
442: * Number of tables that will be generated to store the metaservice,
443: * not counting the root table. This counter will be used to generate
444: * unique names for the tables.
445: */
446: public int numberOfTables = -1;
447:
448: /**
449: * Obtains a field-by-filed copy of this object.
450: *
451: * @return a copy of this object.
452: */
453: public State copy() {
454: State copy = new State();
455: copy.numberOfTables = this.numberOfTables;
456: copy.keyColumn = this.keyColumn;
457: copy.fkColumn = this.fkColumn;
458: return copy;
459: }
460: }
461: }
|