001: /**
002: * Speedo: an implementation of JDO compliant personality on top of JORM generic
003: * I/O sub-system.
004: * Copyright (C) 2001-2004 France Telecom R&D
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: *
021: *
022: * Contact: speedo@objectweb.org
023: *
024: * Authors: S.Chassande-Barrioz.
025: *
026: */package org.objectweb.speedo.metadata;
027:
028: import org.objectweb.jorm.metainfo.api.Class;
029: import org.objectweb.speedo.api.SpeedoException;
030: import org.objectweb.speedo.api.SpeedoRuntimeException;
031: import org.objectweb.speedo.mim.api.HomeItf;
032: import org.objectweb.util.monolog.api.BasicLevel;
033: import org.objectweb.util.monolog.api.Logger;
034:
035: import java.io.File;
036: import java.util.ArrayList;
037: import java.util.Collection;
038: import java.util.Collections;
039: import java.util.HashMap;
040: import java.util.Iterator;
041: import java.util.List;
042: import java.util.Map;
043:
044: /**
045: * Describes a persistence capable class.
046: *
047: * @author S.Chassande-Barrioz
048: */
049: public class SpeedoClass extends SpeedoElement {
050: /**
051: * The class has been previously enhanced and no additional operation is
052: * required.
053: */
054: public final static byte ALREADY_ENHANCED = 1;
055:
056: /**
057: * The enhancement is required.
058: */
059: public final static byte ENHANCEMENT_REQUIRED = 2;
060:
061: /**
062: * The enhancement of the class has failed.
063: */
064: public final static byte ENHANCEMENT_FAILED = 3;
065:
066: /**
067: * The persistent class has no no-arg constructor.
068: */
069: public final static byte NO_NO_ARG_CONSTRUCTOR = 1;
070:
071: /**
072: * The persistent class has no-arg constructor but it is not public
073: */
074: public final static byte NON_PUBLIC_NO_ARG_CONSTRUCTOR = 2;
075:
076: /**
077: * The persistent class has a public no-arg constructor.
078: */
079: public final static byte PUBLIC_NO_ARG_CONSTRUCTOR = 3;
080:
081: /**
082: * Class name.
083: */
084: public String name;
085:
086: /**
087: * Class name for queries.
088: */
089: public String nameForQuery;
090:
091: /**
092: * Description of the package which contains this class.
093: */
094: public SpeedoPackage moPackage;
095:
096: /**
097: * Defines the identifier of the persistent class.
098: */
099: public SpeedoIdentity identity;
100:
101: /**
102: * Defines the field used for database optimitic locking policy.
103: */
104: public SpeedoField versionField;
105:
106: /**
107: * Defines the inheritance strategy if this class inherits from a persistent
108: * class.
109: */
110: public SpeedoInheritance inheritance;
111:
112: /**
113: * Description of the version.
114: */
115: public SpeedoVersion version;
116:
117: /**
118: * Attribute detachable.
119: */
120: public boolean isDetachable;
121:
122: /**
123: * Description of persistent capable fields of this class. The HashMap key
124: * is the field name.
125: */
126: public Map fields;
127:
128: /**
129: * Description of fetchgroups defined for this class. The HashMap key is the
130: * field name.
131: */
132: public Map fetchGroups;
133:
134: /**
135: * Indicates if the class is abstract.
136: */
137: public boolean isAbstract;
138:
139: /**
140: * Indicates if the class implements InstanceCallbacks
141: */
142: public boolean isInstanceCallbacks;
143:
144: /**
145: * Indicates if the class implements Serailizable
146: */
147: public boolean isSerializable;
148:
149: /**
150: * JORM meta object corresponding to this Speedo meta object.
151: */
152: public Class jormclass;
153:
154: /**
155: * Register some callback methods and their associated application method to
156: * which the callback is mapped.
157: * It contains an Integer key identifying a particular callback, which is associated
158: * with an ArrayList of methods to be called when this callback is fired.
159: * Such methods are specified through a metaobject specifying the callback:
160: * @see SpeedoCallback
161: * @see HomeItf#PRE_NEW
162: */
163: public HashMap callBacks;
164:
165: /**
166: * Number of fields of this class (including inherited fields).
167: */
168: private int fieldsCount = -1;
169:
170: /**
171: * Number of fields composing the primary key
172: */
173: private int pkFieldsCount = -1;
174:
175: /**
176: * Specify the status of the persistent class with regards to the
177: * enhancement process. The 3 only possible state are #ALREADY_ENHANCED,
178: * #ENHANCEMENT_REQUIRED or #ENHANCEMENT_FAILED.
179: *
180: * @see #ALREADY_ENHANCED
181: * @see #ENHANCEMENT_REQUIRED
182: * @see #ENHANCEMENT_FAILED
183: */
184: public byte enhancementStatus = ENHANCEMENT_REQUIRED;
185:
186: /**
187: * Contains the predefined query key = a query name value = the
188: * SpeedoPredefinedQuery instance
189: *
190: * @see SpeedoPredefinedQuery
191: */
192: public Map name2query;
193:
194: /**
195: * Qualifies the status of the no-arg constructor of the persistent class.
196: *
197: * @see #NO_NO_ARG_CONSTRUCTOR
198: * @see #NON_PUBLIC_NO_ARG_CONSTRUCTOR
199: * @see #PUBLIC_NO_ARG_CONSTRUCTOR
200: */
201: public byte noArgConstructorStatus = NO_NO_ARG_CONSTRUCTOR;
202:
203: /**
204: * The main table of the persistent class. External tables are reachable
205: * from joins.
206: *
207: * @see #joinToExtTables
208: */
209: public SpeedoTable mainTable;
210:
211: /**
212: * Is the join to reach external tables. It can be null if there is no
213: * external table.
214: */
215: public SpeedoJoin[] joinToExtTables;
216:
217: //TODO: Seb --> remove this method for next commit
218: public String getObjectidClass() {
219: return identity.objectidClass;
220: }
221:
222: /**
223: * @return the fully qualified name of the class (include the package name)
224: * the package separator is a dot
225: */
226: public String getFQName() {
227: if (moPackage.name == null || moPackage.name.length() == 0) {
228: return name;
229: } else {
230: return moPackage.name + "." + name;
231: }
232: }
233:
234: /**
235: * Transforms a SpeedoClass into a String.
236: *
237: * @return the Sting corresponding to the SpeedoClass.
238: */
239: public String toString() {
240: String s = ("\n class name : " + name + ", identityType : "
241: + getIdentityType() + ", objectidClass : "
242: + identity.objectidClass + ",version : " + version
243: + ", persistenceCapableSuperClass : " + getSuperClassName());
244: s += ", \t fields:[";
245: Iterator it = fields.values().iterator();
246: while (it.hasNext()) {
247: s = s + "\t" + it.next().toString();
248: }
249: s += "], \t fetchGroups:[";
250: Iterator it2 = fetchGroups.values().iterator();
251: while (it2.hasNext()) {
252: s += "\t" + it2.next().toString();
253: }
254: s += "]";
255: return s;
256: }
257:
258: public boolean enhancementFailed() {
259: return enhancementStatus == ENHANCEMENT_FAILED;
260: }
261:
262: public boolean isAlreadyEnhanced() {
263: return enhancementStatus == ALREADY_ENHANCED;
264: }
265:
266: public void setAlreadyEnhanced(boolean v) {
267: if (v && enhancementStatus == ENHANCEMENT_REQUIRED) {
268: enhancementStatus = ALREADY_ENHANCED;
269: }
270: }
271:
272: public boolean requireEnhancement() {
273: return enhancementStatus == ENHANCEMENT_REQUIRED;
274: }
275:
276: public void setRequireEnhancement(boolean v) {
277: if (v && enhancementStatus == ALREADY_ENHANCED) {
278: enhancementStatus = ENHANCEMENT_REQUIRED;
279: }
280: }
281:
282: /**
283: * Adds a SpeedoField to the class. Precondition: this field doesn't exist
284: * in the jdoFields HashMap.
285: *
286: * @param field
287: * field to add.
288: */
289: public void add(Object field) {
290: SpeedoField f = (SpeedoField) field;
291: f.moClass = this ;
292: f.number = fieldsCount++;
293: fields.put(f.name, field);
294: }
295:
296: /**
297: * Adds a SpeedoField to the class.
298: *
299: * @param field
300: * field to add.
301: * @param failsOnError
302: * if an error must be thrown or creates a warning.
303: * @param logger
304: * logger for writting warn message if necessary.
305: * @exception SpeedoException
306: * if the field was already defined into the class.
307: */
308: public void add(Object field, boolean failsOnError, Logger logger)
309: throws SpeedoException {
310: SpeedoField f = (SpeedoField) field;
311: if (fields.containsKey(f.name)) {
312: if (failsOnError)
313: throw new SpeedoException("The field " + f.name
314: + " of class " + name + " is defined twice.");
315: else
316: logger.log(BasicLevel.ERROR, "The field " + f.name
317: + " of class " + name + " is defined twice.");
318: } else {
319: logger.log(BasicLevel.DEBUG, "Add the field '" + f.name
320: + "' in class '" + name + "'.");
321: f.moClass = this ;
322: fields.put(f.name, field);
323: }
324: }
325:
326: public void setDatastoreIdSequenceName(String sequenceName) {
327: identity.setDatastoreIdSequenceName(sequenceName);
328: }
329:
330: /**
331: * Adds a SpeedoFetchGroup to the class. Precondition: this fetchgroup
332: * doesn't exist in the jdoFtechGroups HashMap.
333: *
334: * @param fetchGroup
335: * the fetchgroup to add.
336: */
337: public void addFetchGroup(Object fetchGroup) {
338: SpeedoFetchGroup fg = (SpeedoFetchGroup) fetchGroup;
339: fetchGroups.put(fg.name, fetchGroup);
340: }
341:
342: /**
343: * Adds a SpeedoFetchgroup to the class.
344: *
345: * @param fetchGroup
346: * the fetchgroup to add.
347: * @param failsOnError
348: * if an error must be thrown or creates a warning.
349: * @param logger
350: * logger for writting warn message if necessary.
351: * @exception SpeedoException
352: * if the field was already defined into the class.
353: */
354: public void addFetchGroup(Object fetchGroup, boolean failsOnError,
355: Logger logger) throws SpeedoException {
356: SpeedoFetchGroup fg = (SpeedoFetchGroup) fetchGroup;
357: if (fetchGroups.containsKey(fg.name)) {
358: if (failsOnError)
359: throw new SpeedoException("The fetchgroup " + fg.name
360: + " of class " + name + " is defined twice.");
361: else
362: logger.log(BasicLevel.ERROR, "The fetchgroup "
363: + fg.name + " of class " + name
364: + " is defined twice.");
365: } else {
366: logger.log(BasicLevel.DEBUG, "Add the fetchgroup '"
367: + fg.name + "' in class '" + name + "'.");
368: fetchGroups.put(fg.name, fetchGroup);
369: }
370: }
371:
372: /**
373: * Computes the field numbers for the fields of this class.
374: *
375: * @return the number of fields of this class (including inherited fields).
376: */
377: public int computeFieldNumbers() {
378: if (fieldsCount != -1) {
379: return fieldsCount;
380: }
381: if (getSuperClassName() == null) {
382: fieldsCount = 0;
383: } else {
384: SpeedoClass jdoSuperClass = moPackage.xmlDescriptor.smi
385: .getSpeedoClass(getSuperClassName(), moPackage);
386: if (jdoSuperClass == null) {
387: throw personality
388: .newUserRuntimeException("The persistence-capable-superclass field has a bad value: '"
389: + getSuperClassName()
390: + "'. See the description of the class '"
391: + getFQName()
392: + "' from the file '"
393: + getXMLFileName() + "'.");
394: }
395: fieldsCount = jdoSuperClass.computeFieldNumbers();
396: }
397: Iterator i = fields.values().iterator();
398: while (i.hasNext()) {
399: SpeedoField jf = (SpeedoField) i.next();
400: jf.number = fieldsCount++;
401: }
402: return fieldsCount;
403: }
404:
405: public String getJormFileName() {
406: return getFQName().replace('.', File.separatorChar) + ".pd";
407: }
408:
409: public boolean generateObjectId() {
410: return getPkFieldCount() > 1;
411: }
412:
413: public int getPkFieldCount() {
414: if (pkFieldsCount == -1) {
415: pkFieldsCount = 0;
416: if (getIdentityType() == SpeedoIdentity.USER_ID
417: && (identity.objectidClass == null || identity.objectidClass
418: .length() == 0)) {
419: Iterator it = fields.values().iterator();
420: while (it.hasNext()) {
421: if (((SpeedoField) it.next()).primaryKey) {
422: pkFieldsCount++;
423: }
424: }
425: }
426: }
427: return pkFieldsCount;
428: }
429:
430: public List getPKFields() {
431: SpeedoClass parent = getSuper();
432: List idFields;
433: if (parent == null) {
434: idFields = new ArrayList();
435: } else {
436: idFields = parent.getPKFields();
437: }
438: Iterator fieldsIt = fields.values().iterator();
439: while (fieldsIt.hasNext()) {
440: SpeedoField f = (SpeedoField) fieldsIt.next();
441: if (f.primaryKey) {
442: idFields.add(f);
443: }
444: }
445: return idFields;
446: }
447:
448: /**
449: * Find in the class or in its parent, the unique persistent field marked as
450: * primary key.
451: *
452: * @return the unique pk fields if it exists one, null otherwise.
453: * @throws SpeedoException
454: * if there are several persistent fields marked as primary key.
455: */
456: public SpeedoField getUniquePKField() throws SpeedoException {
457: final ArrayList al = new ArrayList();
458: for (Iterator it = fields.values().iterator(); it.hasNext();) {
459: SpeedoField sf = (SpeedoField) it.next();
460: if (sf.primaryKey) {
461: al.add(sf);
462: }
463: }
464: final int s = al.size();
465: if (s == 0) {
466: if (getSuper() != null) {
467: return getSuper().getUniquePKField();
468: }
469: return null;
470: } else if (s > 1) {
471: throw new SpeedoException(
472: "several fields have been marked as primary-key "
473: + al + " in the " + getSourceDesc() + ".");
474: }
475: return (SpeedoField) al.get(0);
476: }
477:
478: public SpeedoClass getSpeedoClassFromContext(String className) {
479: return moPackage.xmlDescriptor.smi.getSpeedoClass(className,
480: moPackage);
481: }
482:
483: public String getXMLFileName() {
484: return moPackage.xmlDescriptor.xmlFile;
485: }
486:
487: public SpeedoClass getSuper() {
488: if (getSuperClassName() == null) {
489: return null;
490: } else {
491: return getSpeedoClassFromContext(getSuperClassName());
492: }
493: }
494:
495: public SpeedoField getInheritedField(String name) {
496: SpeedoClass sc = getSuper();
497: if (sc == null) {
498: return null;
499: }
500: return sc.getField(name);
501: }
502:
503: public SpeedoClass getAncestor() {
504: SpeedoClass tmp = getSuper();
505: SpeedoClass ancestor = null;
506: while (tmp != null && ancestor != tmp) {
507: ancestor = tmp;
508: tmp = tmp.getSuper();
509: }
510: return ancestor;
511: }
512:
513: /**
514: * Finds a field from its name. The fields can belong this class or an
515: * ancestor of this class.
516: *
517: * @param fieldName
518: * is the name of a persistent field. the name can be fully
519: * qualified (ie the field name is prefixed by the class name,
520: * the separator is a dot or #)
521: * @return the SpeedoField instance if it has been found., otherwise null
522: */
523: public SpeedoField getField(String fieldName) {
524: int idx = fieldName.lastIndexOf('.');
525: SpeedoField sf;
526: if (idx == -1) {
527: idx = fieldName.lastIndexOf('#');
528: }
529: if (idx != -1) {
530: // the specified field name is a fully qualified name (include class
531: // name)
532: String className = fieldName.substring(0, idx);
533: String fn = fieldName.substring(idx + 1);
534: SpeedoClass sc = getSpeedoClassFromContext(className);
535: if (sc != null) {
536: // The class has been found, searches in the field in this class
537: // (recusivity)
538: return sc.getField(fn);
539: } else {
540: return null;
541: }
542: }
543: sf = (SpeedoField) fields.get(fieldName);
544: if (sf == null && getSuperClassName() != null) {
545: // try in the parent
546: sf = getSuper().getField(fieldName);
547: }
548: return sf;
549: }
550:
551: public SpeedoField getFieldFromColumn(String colname) {
552: for (Iterator it = fields.values().iterator(); it.hasNext();) {
553: SpeedoField sf = (SpeedoField) it.next();
554: if (sf.columns == null) {
555: continue;
556: }
557: for (int i = 0; i < sf.columns.length; i++) {
558: if (sf.columns[i].table == mainTable
559: && colname.equals(sf.columns[i].name)) {
560: return sf;
561: }
562: }
563: }
564: return null;
565: }
566:
567: public void setIdentityType(byte identityType) {
568: identity.strategy = identityType;
569: }
570:
571: public byte getIdentityType() {
572: return identity.strategy;
573: }
574:
575: public void setSuperClassName(String super ClassName)
576: throws SpeedoException {
577: if (super ClassName != null) {
578: if (inheritance == null) {
579: inheritance = new SpeedoInheritance();
580: inheritance.clazz = this ;
581: }
582: this .inheritance.super ClassName = super ClassName;
583: }
584: }
585:
586: public String getSuperClassName() {
587: if (inheritance == null) {
588: return null;
589: } else {
590: return inheritance.super ClassName;
591: }
592: }
593:
594: public void addJoin(SpeedoJoin j) {
595: joinToExtTables = (SpeedoJoin[]) addInArray(j, joinToExtTables,
596: SpeedoJoin[].class);
597: }
598:
599: public void removeJoin(SpeedoJoin j) {
600: joinToExtTables = (SpeedoJoin[]) removeInArray(j,
601: joinToExtTables, SpeedoJoin[].class);
602: }
603:
604: public String getSourceDesc() {
605: StringBuffer sb = new StringBuffer();
606: sb.append("class '").append(getFQName());
607: sb.append("' in desc '");
608: sb.append(moPackage.xmlDescriptor.xmlFile).append("'");
609: return sb.toString();
610: }
611:
612: public String getSourceDescShort() {
613: StringBuffer sb = new StringBuffer();
614: sb.append("[").append(getFQName()).append("]");
615: return sb.toString();
616: }
617:
618: public SpeedoJoin getJoin(String tableName) {
619: if (joinToExtTables != null) {
620: for (int i = 0; i < joinToExtTables.length; i++) {
621: if (joinToExtTables[i].extTable.name.equals(tableName)) {
622: return joinToExtTables[i];
623: }
624: }
625: }
626: return null;
627: }
628:
629: public SpeedoJoin getJoin(String tableName, boolean createifnone) {
630: SpeedoJoin join = getJoin(tableName);
631: if (createifnone && join == null) {
632: join = new SpeedoJoin();
633: join.mainTable = mainTable;
634: join.extTable = new SpeedoTable();
635: join.extTable.name = tableName;
636: addJoin(join);
637: }
638: return join;
639: }
640:
641: public int getJoinIndex(SpeedoJoin join) {
642: if (joinToExtTables != null) {
643: for (int i = 0; i < joinToExtTables.length; i++) {
644: if (joinToExtTables[i].equals(join)) {
645: return i;
646: }
647: }
648: }
649: return -1;
650: }
651:
652: public boolean containsJoin(SpeedoJoin join) {
653: return getJoinIndex(join) != -1;
654: }
655:
656: public SpeedoTable getExtTable(String tableName,
657: boolean createifnone) {
658: SpeedoJoin join = getJoin(tableName, createifnone);
659: return join == null ? null : join.extTable;
660: }
661:
662: /**
663: * Look for a column with a given name defined into the given SpeedoClass.
664: *
665: * @param colname The name of the column.
666: * @param mainonly Speficy if we must look only for main table columns.
667: * @return The column found or null if none.
668: */
669: public SpeedoColumn getColumn(String colname, boolean mainonly) {
670: for (Iterator it = fields.values().iterator(); it.hasNext();) {
671: SpeedoField sf = (SpeedoField) it.next();
672: if (sf.columns == null) {
673: continue;
674: }
675: for (int i = 0; i < sf.columns.length; i++) {
676: if (mainonly && (sf.columns[i].table != mainTable)) {
677: continue;
678: }
679: if (sf.columns[i].name == null) {
680: continue;
681: }
682: if (colname.equals(sf.columns[i].name)) {
683: return sf.columns[i];
684: }
685: }
686: }
687: return null;
688: }
689:
690: public List getTableIndexes() {
691: ArrayList indexes = new ArrayList();
692: if (mainTable != null) {
693: indexes.addAll(mainTable.indexes);
694: }
695: if (joinToExtTables != null) {
696: //get the indexes of the joins
697: for (int i = 0; i < joinToExtTables.length; i++) {
698: indexes.addAll(joinToExtTables[i].extTable.indexes);
699: }
700: }
701: return indexes;
702: }
703:
704: public List getParents() {
705: SpeedoClass c = getSuper();
706: if (c == null) {
707: return Collections.EMPTY_LIST;
708: }
709: List parents = new ArrayList();
710: while (c != null) {
711: parents.add(0, c);
712: c = c.getSuper();
713: }
714: return parents;
715: }
716: }
|