001: package xdoclet.modules.ojb.constraints;
002:
003: import java.util.HashMap;
004:
005: import xdoclet.modules.ojb.LogHelper;
006: import xdoclet.modules.ojb.model.FieldDescriptorDef;
007: import xdoclet.modules.ojb.model.PropertyHelper;
008:
009: /* Copyright 2004-2005 The Apache Software Foundation
010: *
011: * Licensed under the Apache License, Version 2.0 (the "License");
012: * you may not use this file except in compliance with the License.
013: * You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing, software
018: * distributed under the License is distributed on an "AS IS" BASIS,
019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020: * See the License for the specific language governing permissions and
021: * limitations under the License.
022: */
023:
024: /**
025: * Checks constraints for field descriptors. Note that constraints may modify the field descriptor.
026: *
027: * @author <a href="mailto:tomdz@apache.org">Thomas Dudziak</a>
028: */
029: public class FieldDescriptorConstraints extends
030: FeatureDescriptorConstraints {
031: /** The interface that conversion classes must implement */
032: private final static String CONVERSION_INTERFACE = "org.apache.ojb.broker.accesslayer.conversions.FieldConversion";
033: /** The allowed jdbc types */
034: private HashMap _jdbcTypes = new HashMap();
035:
036: /**
037: * Creates a new field descriptor constraints object.
038: */
039: public FieldDescriptorConstraints() {
040: _jdbcTypes.put("BIT", null);
041: _jdbcTypes.put("TINYINT", null);
042: _jdbcTypes.put("SMALLINT", null);
043: _jdbcTypes.put("INTEGER", null);
044: _jdbcTypes.put("BIGINT", null);
045: _jdbcTypes.put("DOUBLE", null);
046: _jdbcTypes.put("FLOAT", null);
047: _jdbcTypes.put("REAL", null);
048: _jdbcTypes.put("NUMERIC", null);
049: _jdbcTypes.put("DECIMAL", null);
050: _jdbcTypes.put("CHAR", null);
051: _jdbcTypes.put("VARCHAR", null);
052: _jdbcTypes.put("LONGVARCHAR", null);
053: _jdbcTypes.put("DATE", null);
054: _jdbcTypes.put("TIME", null);
055: _jdbcTypes.put("TIMESTAMP", null);
056: _jdbcTypes.put("BINARY", null);
057: _jdbcTypes.put("VARBINARY", null);
058: _jdbcTypes.put("LONGVARBINARY", null);
059: _jdbcTypes.put("CLOB", null);
060: _jdbcTypes.put("BLOB", null);
061: _jdbcTypes.put("STRUCT", null);
062: _jdbcTypes.put("ARRAY", null);
063: _jdbcTypes.put("REF", null);
064: _jdbcTypes.put("BOOLEAN", null);
065: _jdbcTypes.put("DATALINK", null);
066: }
067:
068: /**
069: * Checks the given field descriptor.
070: *
071: * @param fieldDef The field descriptor
072: * @param checkLevel The amount of checks to perform
073: * @exception ConstraintException If a constraint has been violated
074: */
075: public void check(FieldDescriptorDef fieldDef, String checkLevel)
076: throws ConstraintException {
077: ensureColumn(fieldDef, checkLevel);
078: ensureJdbcType(fieldDef, checkLevel);
079: ensureConversion(fieldDef, checkLevel);
080: ensureLength(fieldDef, checkLevel);
081: ensurePrecisionAndScale(fieldDef, checkLevel);
082: checkLocking(fieldDef, checkLevel);
083: checkSequenceName(fieldDef, checkLevel);
084: checkId(fieldDef, checkLevel);
085: if (fieldDef.isAnonymous()) {
086: checkAnonymous(fieldDef, checkLevel);
087: } else {
088: checkReadonlyAccessForNativePKs(fieldDef, checkLevel);
089: }
090: }
091:
092: /**
093: * Constraint that ensures that the field has a column property. If none is specified, then
094: * the name of the field is used.
095: *
096: * @param fieldDef The field descriptor
097: * @param checkLevel The current check level (this constraint is checked in all levels)
098: */
099: private void ensureColumn(FieldDescriptorDef fieldDef,
100: String checkLevel) {
101: if (!fieldDef.hasProperty(PropertyHelper.OJB_PROPERTY_COLUMN)) {
102: String javaname = fieldDef.getName();
103:
104: if (fieldDef.isNested()) {
105: int pos = javaname.indexOf("::");
106:
107: // we convert nested names ('_' for '::')
108: if (pos > 0) {
109: StringBuffer newJavaname = new StringBuffer(
110: javaname.substring(0, pos));
111: int lastPos = pos + 2;
112:
113: do {
114: pos = javaname.indexOf("::", lastPos);
115: newJavaname.append("_");
116: if (pos > 0) {
117: newJavaname.append(javaname.substring(
118: lastPos, pos));
119: lastPos = pos + 2;
120: } else {
121: newJavaname.append(javaname
122: .substring(lastPos));
123: }
124: } while (pos > 0);
125: javaname = newJavaname.toString();
126: }
127: }
128: fieldDef.setProperty(PropertyHelper.OJB_PROPERTY_COLUMN,
129: javaname);
130: }
131: }
132:
133: /**
134: * Constraint that ensures that the field has a jdbc type. If none is specified, then
135: * the default type is used (which has been determined when the field descriptor was added)
136: * and - if necessary - the default conversion is set.
137: *
138: * @param fieldDef The field descriptor
139: * @param checkLevel The current check level (this constraint is checked in all levels)
140: * @exception ConstraintException If the constraint has been violated
141: */
142: private void ensureJdbcType(FieldDescriptorDef fieldDef,
143: String checkLevel) throws ConstraintException {
144: if (!fieldDef
145: .hasProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE)) {
146: if (!fieldDef
147: .hasProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_JDBC_TYPE)) {
148: throw new ConstraintException(
149: "No jdbc-type specified for the field "
150: + fieldDef.getName() + " in class "
151: + fieldDef.getOwner().getName());
152: }
153:
154: fieldDef
155: .setProperty(
156: PropertyHelper.OJB_PROPERTY_JDBC_TYPE,
157: fieldDef
158: .getProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_JDBC_TYPE));
159: if (!fieldDef
160: .hasProperty(PropertyHelper.OJB_PROPERTY_CONVERSION)
161: && fieldDef
162: .hasProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_CONVERSION)) {
163: fieldDef
164: .setProperty(
165: PropertyHelper.OJB_PROPERTY_CONVERSION,
166: fieldDef
167: .getProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_CONVERSION));
168: }
169: } else {
170: // we could let XDoclet check the type for field declarations but not for modifications (as we could
171: // not specify the empty string anymore)
172: String jdbcType = fieldDef
173: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
174:
175: if (!_jdbcTypes.containsKey(jdbcType)) {
176: throw new ConstraintException("The field "
177: + fieldDef.getName() + " in class "
178: + fieldDef.getOwner().getName()
179: + " specifies the invalid jdbc type "
180: + jdbcType);
181: }
182: }
183: }
184:
185: /**
186: * Constraint that ensures that the field has a conversion if the java type requires it. Also checks the conversion class.
187: *
188: * @param fieldDef The field descriptor
189: * @param checkLevel The current check level (this constraint is checked in basic (partly) and strict)
190: * @exception ConstraintException If the conversion class is invalid
191: */
192: private void ensureConversion(FieldDescriptorDef fieldDef,
193: String checkLevel) throws ConstraintException {
194: if (CHECKLEVEL_NONE.equals(checkLevel)) {
195: return;
196: }
197:
198: // we issue a warning if we encounter a field with a java.util.Date java type without a conversion
199: if ("java.util.Date".equals(fieldDef
200: .getProperty(PropertyHelper.OJB_PROPERTY_JAVA_TYPE))
201: && !fieldDef
202: .hasProperty(PropertyHelper.OJB_PROPERTY_CONVERSION)) {
203: LogHelper
204: .warn(
205: true,
206: FieldDescriptorConstraints.class,
207: "ensureConversion",
208: "The field "
209: + fieldDef.getName()
210: + " in class "
211: + fieldDef.getOwner().getName()
212: + " of type java.util.Date is directly mapped to jdbc-type "
213: + fieldDef
214: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE)
215: + ". However, most JDBC drivers can't handle java.util.Date directly so you might want to "
216: + " use a conversion for converting it to a JDBC datatype like TIMESTAMP.");
217: }
218:
219: String conversionClass = fieldDef
220: .getProperty(PropertyHelper.OJB_PROPERTY_CONVERSION);
221:
222: if (((conversionClass == null) || (conversionClass.length() == 0))
223: && fieldDef
224: .hasProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_CONVERSION)
225: && fieldDef
226: .getProperty(
227: PropertyHelper.OJB_PROPERTY_DEFAULT_JDBC_TYPE)
228: .equals(
229: fieldDef
230: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE))) {
231: conversionClass = fieldDef
232: .getProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_CONVERSION);
233: fieldDef.setProperty(
234: PropertyHelper.OJB_PROPERTY_CONVERSION,
235: conversionClass);
236: }
237: // now checking
238: if (CHECKLEVEL_STRICT.equals(checkLevel)
239: && (conversionClass != null)
240: && (conversionClass.length() > 0)) {
241: InheritanceHelper helper = new InheritanceHelper();
242:
243: try {
244: if (!helper.isSameOrSubTypeOf(conversionClass,
245: CONVERSION_INTERFACE)) {
246: throw new ConstraintException(
247: "The conversion class specified for field "
248: + fieldDef.getName()
249: + " in class "
250: + fieldDef.getOwner().getName()
251: + " does not implement the necessary interface "
252: + CONVERSION_INTERFACE);
253: }
254: } catch (ClassNotFoundException ex) {
255: throw new ConstraintException(
256: "The class "
257: + ex.getMessage()
258: + " hasn't been found on the classpath while checking the conversion class specified for field "
259: + fieldDef.getName() + " in class "
260: + fieldDef.getOwner().getName());
261: }
262: }
263: }
264:
265: /**
266: * Constraint that ensures that the field has a length if the jdbc type requires it.
267: *
268: * @param fieldDef The field descriptor
269: * @param checkLevel The current check level (this constraint is checked in all levels)
270: */
271: private void ensureLength(FieldDescriptorDef fieldDef,
272: String checkLevel) {
273: if (!fieldDef.hasProperty(PropertyHelper.OJB_PROPERTY_LENGTH)) {
274: String defaultLength = JdbcTypeHelper
275: .getDefaultLengthFor(fieldDef
276: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
277:
278: if (defaultLength != null) {
279: LogHelper
280: .warn(
281: true,
282: FieldDescriptorConstraints.class,
283: "ensureLength",
284: "The field "
285: + fieldDef.getName()
286: + " in class "
287: + fieldDef.getOwner().getName()
288: + " has no length setting though its jdbc type requires it (in most databases); using default length of "
289: + defaultLength);
290: fieldDef.setProperty(
291: PropertyHelper.OJB_PROPERTY_LENGTH,
292: defaultLength);
293: }
294: }
295: }
296:
297: /**
298: * Constraint that ensures that the field has precision and scale settings if the jdbc type requires it.
299: *
300: * @param fieldDef The field descriptor
301: * @param checkLevel The current check level (this constraint is checked in all levels)
302: */
303: private void ensurePrecisionAndScale(FieldDescriptorDef fieldDef,
304: String checkLevel) {
305: fieldDef.setProperty(
306: PropertyHelper.OJB_PROPERTY_DEFAULT_PRECISION, null);
307: fieldDef.setProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_SCALE,
308: null);
309: if (!fieldDef
310: .hasProperty(PropertyHelper.OJB_PROPERTY_PRECISION)) {
311: String defaultPrecision = JdbcTypeHelper
312: .getDefaultPrecisionFor(fieldDef
313: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
314:
315: if (defaultPrecision != null) {
316: LogHelper
317: .warn(
318: true,
319: FieldDescriptorConstraints.class,
320: "ensureLength",
321: "The field "
322: + fieldDef.getName()
323: + " in class "
324: + fieldDef.getOwner().getName()
325: + " has no precision setting though its jdbc type requires it (in most databases); using default precision of "
326: + defaultPrecision);
327: fieldDef.setProperty(
328: PropertyHelper.OJB_PROPERTY_DEFAULT_PRECISION,
329: defaultPrecision);
330: } else if (fieldDef
331: .hasProperty(PropertyHelper.OJB_PROPERTY_SCALE)) {
332: fieldDef.setProperty(
333: PropertyHelper.OJB_PROPERTY_DEFAULT_PRECISION,
334: "1");
335: }
336: }
337: if (!fieldDef.hasProperty(PropertyHelper.OJB_PROPERTY_SCALE)) {
338: String defaultScale = JdbcTypeHelper
339: .getDefaultScaleFor(fieldDef
340: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
341:
342: if (defaultScale != null) {
343: LogHelper
344: .warn(
345: true,
346: FieldDescriptorConstraints.class,
347: "ensureLength",
348: "The field "
349: + fieldDef.getName()
350: + " in class "
351: + fieldDef.getOwner().getName()
352: + " has no scale setting though its jdbc type requires it (in most databases); using default scale of "
353: + defaultScale);
354: fieldDef.setProperty(
355: PropertyHelper.OJB_PROPERTY_DEFAULT_SCALE,
356: defaultScale);
357: } else if (fieldDef
358: .hasProperty(PropertyHelper.OJB_PROPERTY_PRECISION)
359: || fieldDef
360: .hasProperty(PropertyHelper.OJB_PROPERTY_DEFAULT_PRECISION)) {
361: fieldDef.setProperty(
362: PropertyHelper.OJB_PROPERTY_DEFAULT_SCALE, "0");
363: }
364: }
365: }
366:
367: /**
368: * Checks that locking and update-lock are only used for fields of TIMESTAMP or INTEGER type.
369: *
370: * @param fieldDef The field descriptor
371: * @param checkLevel The current check level (this constraint is checked in basic and strict)
372: * @exception ConstraintException If the constraint has been violated
373: */
374: private void checkLocking(FieldDescriptorDef fieldDef,
375: String checkLevel) throws ConstraintException {
376: if (CHECKLEVEL_NONE.equals(checkLevel)) {
377: return;
378: }
379:
380: String jdbcType = fieldDef
381: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
382:
383: if (!"TIMESTAMP".equals(jdbcType)
384: && !"INTEGER".equals(jdbcType)) {
385: if (fieldDef.getBooleanProperty(
386: PropertyHelper.OJB_PROPERTY_LOCKING, false)) {
387: throw new ConstraintException(
388: "The field "
389: + fieldDef.getName()
390: + " in class "
391: + fieldDef.getOwner().getName()
392: + " has locking set to true though it is not of TIMESTAMP or INTEGER type");
393: }
394: if (fieldDef.getBooleanProperty(
395: PropertyHelper.OJB_PROPERTY_UPDATE_LOCK, false)) {
396: throw new ConstraintException(
397: "The field "
398: + fieldDef.getName()
399: + " in class "
400: + fieldDef.getOwner().getName()
401: + " has update-lock set to true though it is not of TIMESTAMP or INTEGER type");
402: }
403: }
404: }
405:
406: /**
407: * Checks that sequence-name is only used with autoincrement='ojb'
408: *
409: * @param fieldDef The field descriptor
410: * @param checkLevel The current check level (this constraint is checked in basic and strict)
411: * @exception ConstraintException If the constraint has been violated
412: */
413: private void checkSequenceName(FieldDescriptorDef fieldDef,
414: String checkLevel) throws ConstraintException {
415: if (CHECKLEVEL_NONE.equals(checkLevel)) {
416: return;
417: }
418:
419: String autoIncr = fieldDef
420: .getProperty(PropertyHelper.OJB_PROPERTY_AUTOINCREMENT);
421: String seqName = fieldDef
422: .getProperty(PropertyHelper.OJB_PROPERTY_SEQUENCE_NAME);
423:
424: if ((seqName != null) && (seqName.length() > 0)) {
425: if (!"ojb".equals(autoIncr) && !"database".equals(autoIncr)) {
426: throw new ConstraintException(
427: "The field "
428: + fieldDef.getName()
429: + " in class "
430: + fieldDef.getOwner().getName()
431: + " has sequence-name set though it's autoincrement value is not set to 'ojb'");
432: }
433: }
434: }
435:
436: /**
437: * Checks the id value.
438: *
439: * @param fieldDef The field descriptor
440: * @param checkLevel The current check level (this constraint is checked in basic and strict)
441: * @exception ConstraintException If the constraint has been violated
442: */
443: private void checkId(FieldDescriptorDef fieldDef, String checkLevel)
444: throws ConstraintException {
445: if (CHECKLEVEL_NONE.equals(checkLevel)) {
446: return;
447: }
448:
449: String id = fieldDef
450: .getProperty(PropertyHelper.OJB_PROPERTY_ID);
451:
452: if ((id != null) && (id.length() > 0)) {
453: try {
454: Integer.parseInt(id);
455: } catch (NumberFormatException ex) {
456: throw new ConstraintException(
457: "The id attribute of field "
458: + fieldDef.getName() + " in class "
459: + fieldDef.getOwner().getName()
460: + " is not a valid number");
461: }
462: }
463: }
464:
465: /**
466: * Checks that native primarykey fields have readonly access, and warns if not.
467: *
468: * @param fieldDef The field descriptor
469: * @param checkLevel The current check level (this constraint is checked in basic and strict)
470: */
471: private void checkReadonlyAccessForNativePKs(
472: FieldDescriptorDef fieldDef, String checkLevel) {
473: if (CHECKLEVEL_NONE.equals(checkLevel)) {
474: return;
475: }
476:
477: String access = fieldDef
478: .getProperty(PropertyHelper.OJB_PROPERTY_ACCESS);
479: String autoInc = fieldDef
480: .getProperty(PropertyHelper.OJB_PROPERTY_AUTOINCREMENT);
481:
482: if ("database".equals(autoInc) && !"readonly".equals(access)) {
483: LogHelper
484: .warn(
485: true,
486: FieldDescriptorConstraints.class,
487: "checkAccess",
488: "The field "
489: + fieldDef.getName()
490: + " in class "
491: + fieldDef.getOwner().getName()
492: + " is set to database auto-increment. Therefore the field's access is set to 'readonly'.");
493: fieldDef.setProperty(PropertyHelper.OJB_PROPERTY_ACCESS,
494: "readonly");
495: }
496: }
497:
498: /**
499: * Checks anonymous fields.
500: *
501: * @param fieldDef The field descriptor
502: * @param checkLevel The current check level (this constraint is checked in basic and strict)
503: * @exception ConstraintException If the constraint has been violated
504: */
505: private void checkAnonymous(FieldDescriptorDef fieldDef,
506: String checkLevel) throws ConstraintException {
507: if (CHECKLEVEL_NONE.equals(checkLevel)) {
508: return;
509: }
510:
511: String access = fieldDef
512: .getProperty(PropertyHelper.OJB_PROPERTY_ACCESS);
513:
514: if (!"anonymous".equals(access)) {
515: throw new ConstraintException(
516: "The access property of the field "
517: + fieldDef.getName() + " defined in class "
518: + fieldDef.getOwner().getName()
519: + " cannot be changed");
520: }
521:
522: if ((fieldDef.getName() == null)
523: || (fieldDef.getName().length() == 0)) {
524: throw new ConstraintException(
525: "An anonymous field defined in class "
526: + fieldDef.getOwner().getName()
527: + " has no name");
528: }
529: }
530: }
|