001: package xdoclet.modules.ojb.constraints;
002:
003: /* Copyright 2004-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.lang.reflect.Method;
019: import java.lang.reflect.Modifier;
020: import java.util.*;
021:
022: import xdoclet.modules.ojb.CommaListIterator;
023: import xdoclet.modules.ojb.LogHelper;
024: import xdoclet.modules.ojb.model.*;
025:
026: /**
027: * Checks constraints for class descriptors. Note that constraints may modify the class descriptor.
028: *
029: * @author <a href="mailto:tomdz@users.sourceforge.net">Thomas Dudziak (tomdz@users.sourceforge.net)</a>
030: */
031: public class ClassDescriptorConstraints extends ConstraintsBase {
032: /** The interface that row readers must implement */
033: private final static String ROW_READER_INTERFACE = "org.apache.ojb.broker.accesslayer.RowReader";
034: /** The interface that object caches must implement */
035: private final static String OBJECT_CACHE_INTERFACE = "org.apache.ojb.broker.cache.ObjectCache";
036:
037: /**
038: * Checks the given class descriptor.
039: *
040: * @param classDef The class descriptor
041: * @param checkLevel The amount of checks to perform
042: * @exception ConstraintException If a constraint has been violated
043: */
044: public void check(ClassDescriptorDef classDef, String checkLevel)
045: throws ConstraintException {
046: ensureNoTableInfoIfNoRepositoryInfo(classDef, checkLevel);
047: checkModifications(classDef, checkLevel);
048: checkExtents(classDef, checkLevel);
049: ensureTableIfNecessary(classDef, checkLevel);
050: checkFactoryClassAndMethod(classDef, checkLevel);
051: checkInitializationMethod(classDef, checkLevel);
052: checkPrimaryKey(classDef, checkLevel);
053: checkProxyPrefetchingLimit(classDef, checkLevel);
054: checkRowReader(classDef, checkLevel);
055: checkObjectCache(classDef, checkLevel);
056: checkProcedures(classDef, checkLevel);
057: }
058:
059: /**
060: * Ensures that generate-table-info is set to false if generate-repository-info is set to false.
061: *
062: * @param classDef The class descriptor
063: * @param checkLevel The current check level (this constraint is checked in all levels)
064: */
065: private void ensureNoTableInfoIfNoRepositoryInfo(
066: ClassDescriptorDef classDef, String checkLevel) {
067: if (!classDef.getBooleanProperty(
068: PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO,
069: true)) {
070: classDef.setProperty(
071: PropertyHelper.OJB_PROPERTY_GENERATE_TABLE_INFO,
072: "false");
073: }
074: }
075:
076: /**
077: * Checks that the modified features exist.
078: *
079: * @param classDef The class descriptor
080: * @param checkLevel The current check level (this constraint is checked in basic and strict)
081: * @exception ConstraintException If the constraint has been violated
082: */
083: private void checkModifications(ClassDescriptorDef classDef,
084: String checkLevel) throws ConstraintException {
085: if (CHECKLEVEL_NONE.equals(checkLevel)) {
086: return;
087: }
088:
089: HashMap features = new HashMap();
090: FeatureDescriptorDef def;
091:
092: for (Iterator it = classDef.getFields(); it.hasNext();) {
093: def = (FeatureDescriptorDef) it.next();
094: features.put(def.getName(), def);
095: }
096: for (Iterator it = classDef.getReferences(); it.hasNext();) {
097: def = (FeatureDescriptorDef) it.next();
098: features.put(def.getName(), def);
099: }
100: for (Iterator it = classDef.getCollections(); it.hasNext();) {
101: def = (FeatureDescriptorDef) it.next();
102: features.put(def.getName(), def);
103: }
104:
105: // now checking the modifications
106: Properties mods;
107: String modName;
108: String propName;
109:
110: for (Iterator it = classDef.getModificationNames(); it
111: .hasNext();) {
112: modName = (String) it.next();
113: if (!features.containsKey(modName)) {
114: throw new ConstraintException(
115: "Class "
116: + classDef.getName()
117: + " contains a modification for an unknown feature "
118: + modName);
119: }
120: def = (FeatureDescriptorDef) features.get(modName);
121: if (def.getOriginal() == null) {
122: throw new ConstraintException(
123: "Class "
124: + classDef.getName()
125: + " contains a modification for a feature "
126: + modName
127: + " that is not inherited but defined in the same class");
128: }
129: // checking modification
130: mods = classDef.getModification(modName);
131: for (Iterator propIt = mods.keySet().iterator(); propIt
132: .hasNext();) {
133: propName = (String) propIt.next();
134: if (!PropertyHelper.isPropertyAllowed(def.getClass(),
135: propName)) {
136: throw new ConstraintException(
137: "The modification of attribute "
138: + propName
139: + " in class "
140: + classDef.getName()
141: + " is not applicable to the feature "
142: + modName);
143: }
144: }
145: }
146: }
147:
148: /**
149: * Checks the extents specifications and removes unnecessary entries.
150: *
151: * @param classDef The class descriptor
152: * @param checkLevel The current check level (this constraint is checked in basic and strict)
153: * @exception ConstraintException If the constraint has been violated
154: */
155: private void checkExtents(ClassDescriptorDef classDef,
156: String checkLevel) throws ConstraintException {
157: if (CHECKLEVEL_NONE.equals(checkLevel)) {
158: return;
159: }
160:
161: HashMap processedClasses = new HashMap();
162: InheritanceHelper helper = new InheritanceHelper();
163: ClassDescriptorDef curExtent;
164: boolean canBeRemoved;
165:
166: for (Iterator it = classDef.getExtentClasses(); it.hasNext();) {
167: curExtent = (ClassDescriptorDef) it.next();
168: canBeRemoved = false;
169: if (classDef.getName().equals(curExtent.getName())) {
170: throw new ConstraintException("The class "
171: + classDef.getName()
172: + " specifies itself as an extent-class");
173: } else if (processedClasses.containsKey(curExtent)) {
174: canBeRemoved = true;
175: } else {
176: try {
177: if (!helper.isSameOrSubTypeOf(curExtent, classDef
178: .getName(), false)) {
179: throw new ConstraintException("The class "
180: + classDef.getName()
181: + " specifies an extent-class "
182: + curExtent.getName()
183: + " that is not a sub-type of it");
184: }
185: // now we check whether we already have an extent for a base-class of this extent-class
186: for (Iterator processedIt = processedClasses
187: .keySet().iterator(); processedIt.hasNext();) {
188: if (helper.isSameOrSubTypeOf(curExtent,
189: ((ClassDescriptorDef) processedIt
190: .next()).getName(), false)) {
191: canBeRemoved = true;
192: break;
193: }
194: }
195: } catch (ClassNotFoundException ex) {
196: // won't happen because we don't use lookup of the actual classes
197: }
198: }
199: if (canBeRemoved) {
200: it.remove();
201: }
202: processedClasses.put(curExtent, null);
203: }
204: }
205:
206: /**
207: * Makes sure that the class descriptor has a table attribute if it requires it (i.e. it is
208: * relevant for the repository descriptor).
209: *
210: * @param classDef The class descriptor
211: * @param checkLevel The current check level (this constraint is checked in all levels)
212: */
213: private void ensureTableIfNecessary(ClassDescriptorDef classDef,
214: String checkLevel) {
215: if (classDef.getBooleanProperty(
216: PropertyHelper.OJB_PROPERTY_OJB_PERSISTENT, false)) {
217: if (!classDef
218: .hasProperty(PropertyHelper.OJB_PROPERTY_TABLE)) {
219: classDef.setProperty(PropertyHelper.OJB_PROPERTY_TABLE,
220: classDef.getDefaultTableName());
221: }
222: }
223: }
224:
225: /**
226: * Checks the given class descriptor for correct factory-class and factory-method.
227: *
228: * @param classDef The class descriptor
229: * @param checkLevel The current check level (this constraint is checked in basic (partly) and strict)
230: * @exception ConstraintException If the constraint has been violated
231: */
232: private void checkFactoryClassAndMethod(
233: ClassDescriptorDef classDef, String checkLevel)
234: throws ConstraintException {
235: if (CHECKLEVEL_NONE.equals(checkLevel)) {
236: return;
237: }
238: String factoryClassName = classDef
239: .getProperty(PropertyHelper.OJB_PROPERTY_FACTORY_CLASS);
240: String factoryMethodName = classDef
241: .getProperty(PropertyHelper.OJB_PROPERTY_FACTORY_METHOD);
242:
243: if ((factoryClassName == null) && (factoryMethodName == null)) {
244: return;
245: }
246: if ((factoryClassName != null) && (factoryMethodName == null)) {
247: throw new ConstraintException("Class " + classDef.getName()
248: + " has a factory-class but no factory-method.");
249: }
250: if ((factoryClassName == null) && (factoryMethodName != null)) {
251: throw new ConstraintException("Class " + classDef.getName()
252: + " has a factory-method but no factory-class.");
253: }
254:
255: if (CHECKLEVEL_STRICT.equals(checkLevel)) {
256: Class factoryClass;
257: Method factoryMethod;
258:
259: try {
260: factoryClass = InheritanceHelper
261: .getClass(factoryClassName);
262: } catch (ClassNotFoundException ex) {
263: throw new ConstraintException("The class "
264: + factoryClassName
265: + " specified as factory-class of class "
266: + classDef.getName()
267: + " was not found on the classpath");
268: }
269: try {
270: factoryMethod = factoryClass.getDeclaredMethod(
271: factoryMethodName, new Class[0]);
272: } catch (NoSuchMethodException ex) {
273: factoryMethod = null;
274: } catch (Exception ex) {
275: throw new ConstraintException(
276: "Exception while checking the factory-class "
277: + factoryClassName + " of class "
278: + classDef.getName() + ": "
279: + ex.getMessage());
280: }
281: if (factoryMethod == null) {
282: try {
283: factoryMethod = factoryClass.getMethod(
284: factoryMethodName, new Class[0]);
285: } catch (NoSuchMethodException ex) {
286: throw new ConstraintException(
287: "No suitable factory-method "
288: + factoryMethodName
289: + " found in the factory-class "
290: + factoryClassName + " of class "
291: + classDef.getName());
292: } catch (Exception ex) {
293: throw new ConstraintException(
294: "Exception while checking the factory-class "
295: + factoryClassName + " of class "
296: + classDef.getName() + ": "
297: + ex.getMessage());
298: }
299: }
300:
301: // checking return type and modifiers
302: Class returnType = factoryMethod.getReturnType();
303: InheritanceHelper helper = new InheritanceHelper();
304:
305: if ("void".equals(returnType.getName())) {
306: throw new ConstraintException("The factory-method "
307: + factoryMethodName + " in factory-class "
308: + factoryClassName + " of class "
309: + classDef.getName() + " must return a value");
310: }
311: try {
312: if (!helper.isSameOrSubTypeOf(returnType.getName(),
313: classDef.getName())) {
314: throw new ConstraintException("The method "
315: + factoryMethodName + " in factory-class "
316: + factoryClassName + " of class "
317: + classDef.getName()
318: + " must return the type "
319: + classDef.getName()
320: + " or a subtype of it");
321: }
322: } catch (ClassNotFoundException ex) {
323: throw new ConstraintException(
324: "Could not find the class "
325: + ex.getMessage()
326: + " on the classpath while checking the factory-method "
327: + factoryMethodName
328: + " in the factory-class "
329: + factoryClassName + " of class "
330: + classDef.getName());
331: }
332:
333: if (!Modifier.isStatic(factoryMethod.getModifiers())) {
334: throw new ConstraintException("The factory-method "
335: + factoryMethodName + " in factory-class "
336: + factoryClassName + " of class "
337: + classDef.getName() + " must be static");
338: }
339: }
340: }
341:
342: /**
343: * Checks the initialization-method of given class descriptor.
344: *
345: * @param classDef The class descriptor
346: * @param checkLevel The current check level (this constraint is only checked in strict)
347: * @exception ConstraintException If the constraint has been violated
348: */
349: private void checkInitializationMethod(ClassDescriptorDef classDef,
350: String checkLevel) throws ConstraintException {
351: if (!CHECKLEVEL_STRICT.equals(checkLevel)) {
352: return;
353: }
354:
355: String initMethodName = classDef
356: .getProperty(PropertyHelper.OJB_PROPERTY_INITIALIZATION_METHOD);
357:
358: if (initMethodName == null) {
359: return;
360: }
361:
362: Class initClass;
363: Method initMethod;
364:
365: try {
366: initClass = InheritanceHelper.getClass(classDef.getName());
367: } catch (ClassNotFoundException ex) {
368: throw new ConstraintException("The class "
369: + classDef.getName()
370: + " was not found on the classpath");
371: }
372: try {
373: initMethod = initClass.getDeclaredMethod(initMethodName,
374: new Class[0]);
375: } catch (NoSuchMethodException ex) {
376: initMethod = null;
377: } catch (Exception ex) {
378: throw new ConstraintException(
379: "Exception while checking the class "
380: + classDef.getName() + ": "
381: + ex.getMessage());
382: }
383: if (initMethod == null) {
384: try {
385: initMethod = initClass.getMethod(initMethodName,
386: new Class[0]);
387: } catch (NoSuchMethodException ex) {
388: throw new ConstraintException(
389: "No suitable initialization-method "
390: + initMethodName + " found in class "
391: + classDef.getName());
392: } catch (Exception ex) {
393: throw new ConstraintException(
394: "Exception while checking the class "
395: + classDef.getName() + ": "
396: + ex.getMessage());
397: }
398: }
399:
400: // checking modifiers
401: int mods = initMethod.getModifiers();
402:
403: if (Modifier.isStatic(mods) || Modifier.isAbstract(mods)) {
404: throw new ConstraintException("The initialization-method "
405: + initMethodName + " in class "
406: + classDef.getName()
407: + " must be a concrete instance method");
408: }
409: }
410:
411: /**
412: * Checks whether given class descriptor has a primary key.
413: *
414: * @param classDef The class descriptor
415: * @param checkLevel The current check level (this constraint is only checked in strict)
416: * @exception ConstraintException If the constraint has been violated
417: */
418: private void checkPrimaryKey(ClassDescriptorDef classDef,
419: String checkLevel) throws ConstraintException {
420: if (CHECKLEVEL_NONE.equals(checkLevel)) {
421: return;
422: }
423:
424: if (classDef.getBooleanProperty(
425: PropertyHelper.OJB_PROPERTY_GENERATE_TABLE_INFO, true)
426: && classDef.getPrimaryKeys().isEmpty()) {
427: LogHelper.warn(true, getClass(), "checkPrimaryKey",
428: "The class " + classDef.getName()
429: + " has no primary key");
430: }
431: }
432:
433: /**
434: * Checks the given class descriptor for correct row-reader setting.
435: *
436: * @param classDef The class descriptor
437: * @param checkLevel The current check level (this constraint is only checked in strict)
438: * @exception ConstraintException If the constraint has been violated
439: */
440: private void checkRowReader(ClassDescriptorDef classDef,
441: String checkLevel) throws ConstraintException {
442: if (!CHECKLEVEL_STRICT.equals(checkLevel)) {
443: return;
444: }
445:
446: String rowReaderName = classDef
447: .getProperty(PropertyHelper.OJB_PROPERTY_ROW_READER);
448:
449: if (rowReaderName == null) {
450: return;
451: }
452:
453: try {
454: InheritanceHelper helper = new InheritanceHelper();
455:
456: if (!helper.isSameOrSubTypeOf(rowReaderName,
457: ROW_READER_INTERFACE)) {
458: throw new ConstraintException("The class "
459: + rowReaderName
460: + " specified as row-reader of class "
461: + classDef.getName()
462: + " does not implement the interface "
463: + ROW_READER_INTERFACE);
464: }
465: } catch (ClassNotFoundException ex) {
466: throw new ConstraintException(
467: "Could not find the class "
468: + ex.getMessage()
469: + " on the classpath while checking the row-reader class "
470: + rowReaderName + " of class "
471: + classDef.getName());
472: }
473: }
474:
475: /**
476: * Checks the given class descriptor for correct object cache setting.
477: *
478: * @param classDef The class descriptor
479: * @param checkLevel The current check level (this constraint is only checked in strict)
480: * @exception ConstraintException If the constraint has been violated
481: */
482: private void checkObjectCache(ClassDescriptorDef classDef,
483: String checkLevel) throws ConstraintException {
484: if (!CHECKLEVEL_STRICT.equals(checkLevel)) {
485: return;
486: }
487:
488: ObjectCacheDef objCacheDef = classDef.getObjectCache();
489:
490: if (objCacheDef == null) {
491: return;
492: }
493:
494: String objectCacheName = objCacheDef.getName();
495:
496: if ((objectCacheName == null)
497: || (objectCacheName.length() == 0)) {
498: throw new ConstraintException(
499: "No class specified for the object-cache of class "
500: + classDef.getName());
501: }
502:
503: try {
504: InheritanceHelper helper = new InheritanceHelper();
505:
506: if (!helper.isSameOrSubTypeOf(objectCacheName,
507: OBJECT_CACHE_INTERFACE)) {
508: throw new ConstraintException("The class "
509: + objectCacheName
510: + " specified as object-cache of class "
511: + classDef.getName()
512: + " does not implement the interface "
513: + OBJECT_CACHE_INTERFACE);
514: }
515: } catch (ClassNotFoundException ex) {
516: throw new ConstraintException(
517: "Could not find the class "
518: + ex.getMessage()
519: + " on the classpath while checking the object-cache class "
520: + objectCacheName + " of class "
521: + classDef.getName());
522: }
523: }
524:
525: /**
526: * Checks the given class descriptor for correct procedure settings.
527: *
528: * @param classDef The class descriptor
529: * @param checkLevel The current check level (this constraint is checked in basic and strict)
530: * @exception ConstraintException If the constraint has been violated
531: */
532: private void checkProcedures(ClassDescriptorDef classDef,
533: String checkLevel) throws ConstraintException {
534: if (CHECKLEVEL_NONE.equals(checkLevel)) {
535: return;
536: }
537:
538: ProcedureDef procDef;
539: String type;
540: String name;
541: String fieldName;
542: String argName;
543:
544: for (Iterator it = classDef.getProcedures(); it.hasNext();) {
545: procDef = (ProcedureDef) it.next();
546: type = procDef.getName();
547: name = procDef
548: .getProperty(PropertyHelper.OJB_PROPERTY_NAME);
549: if ((name == null) || (name.length() == 0)) {
550: throw new ConstraintException("The " + type
551: + "-procedure in class " + classDef.getName()
552: + " doesn't have a name");
553: }
554: fieldName = procDef
555: .getProperty(PropertyHelper.OJB_PROPERTY_RETURN_FIELD_REF);
556: if ((fieldName != null) && (fieldName.length() > 0)) {
557: if (classDef.getField(fieldName) == null) {
558: throw new ConstraintException(
559: "The "
560: + type
561: + "-procedure "
562: + name
563: + " in class "
564: + classDef.getName()
565: + " references an unknown or non-persistent return field "
566: + fieldName);
567: }
568: }
569: for (CommaListIterator argIt = new CommaListIterator(
570: procDef
571: .getProperty(PropertyHelper.OJB_PROPERTY_ARGUMENTS)); argIt
572: .hasNext();) {
573: argName = argIt.getNext();
574: if (classDef.getProcedureArgument(argName) == null) {
575: throw new ConstraintException("The " + type
576: + "-procedure " + name + " in class "
577: + classDef.getName()
578: + " references an unknown argument "
579: + argName);
580: }
581: }
582: }
583:
584: ProcedureArgumentDef argDef;
585:
586: for (Iterator it = classDef.getProcedureArguments(); it
587: .hasNext();) {
588: argDef = (ProcedureArgumentDef) it.next();
589: type = argDef.getProperty(PropertyHelper.OJB_PROPERTY_TYPE);
590: if ("runtime".equals(type)) {
591: fieldName = argDef
592: .getProperty(PropertyHelper.OJB_PROPERTY_FIELD_REF);
593: if ((fieldName != null) && (fieldName.length() > 0)) {
594: if (classDef.getField(fieldName) == null) {
595: throw new ConstraintException(
596: "The "
597: + type
598: + "-argument "
599: + argDef.getName()
600: + " in class "
601: + classDef.getName()
602: + " references an unknown or non-persistent return field "
603: + fieldName);
604: }
605: }
606: }
607: }
608: }
609: }
|