1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 package pl.kernelpanic.dbmonster.schema;
48
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.InputStream;
52 import java.io.Writer;
53 import java.lang.reflect.Method;
54 import java.lang.reflect.Modifier;
55 import java.net.URL;
56 import java.util.ArrayList;
57 import java.util.Collections;
58 import java.util.HashMap;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Map;
62
63 import pl.kernelpanic.dbmonster.generator.DataGenerator;
64 import pl.kernelpanic.dbmonster.generator.KeyGenerator;
65
66 import org.apache.commons.beanutils.BeanUtils;
67 import org.apache.commons.digester.AbstractObjectCreationFactory;
68 import org.apache.commons.digester.Digester;
69 import org.apache.commons.logging.Log;
70 import org.xml.sax.Attributes;
71 import org.xml.sax.ErrorHandler;
72 import org.xml.sax.SAXException;
73 import org.xml.sax.SAXParseException;
74
75 /***
76 * Utility to manipulate schema.
77 *
78 * @author Piotr Maj <piotr.maj@kernelpanic.pl>
79 *
80 * @version $Id: SchemaUtil.html,v 1.1 2007/06/21 08:38:14 sbahloul Exp $
81 */
82 public final class SchemaUtil {
83
84 /***
85 * DTD identifier.
86 */
87 public static final String DTD =
88 "-//kernelpanic.pl//DBMonster Database Schema DTD 1.1//EN";
89
90 /***
91 * DTD url.
92 */
93 public static final String DTD_URL =
94 "http://dbmonster.kernelpanic.pl/dtd/dbmonster-schema-1.1.dtd";
95
96 /***
97 * Project key.
98 */
99 public static final String PROJECT = "project";
100
101 /***
102 * Schema key.
103 */
104 public static final String SCHEMA_WRAPPER = "schema";
105
106 /***
107 * Table key.
108 */
109 public static final String TABLE = "table";
110
111 /***
112 * Column key.
113 */
114 public static final String COLUMN = "column";
115
116 /***
117 * Primary key key.
118 */
119 public static final String KEY = "key";
120
121 /***
122 * Key generator key.
123 */
124 public static final String KEY_GENERATOR = "key_generator";
125
126 /***
127 * Data generator key.
128 */
129 public static final String DATA_GENERATOR = "data_generator";
130
131 /***
132 * Holds the excluded property names.
133 */
134 private static Map exclusions = new HashMap();
135
136 /***
137 * System dependent line separator.
138 */
139 public static final String CRLF = System.getProperty("line.separator");
140
141 static {
142 exclusions.put(SchemaUtil.PROJECT,
143 new String[] {"fileName", "properties", "jdbcViaProperties"});
144 exclusions.put(SchemaUtil.SCHEMA_WRAPPER,
145 new String[] {"fileName", "schema"});
146 exclusions.put(SchemaUtil.TABLE,
147 new String[] {"key", "schema"});
148 exclusions.put(SchemaUtil.KEY,
149 new String[] {"table", "generator"});
150 exclusions.put(SchemaUtil.COLUMN,
151 new String[] {"table", "generator", "value"});
152 exclusions.put(SchemaUtil.KEY_GENERATOR,
153 new String[] {"key"});
154 exclusions.put(SchemaUtil.DATA_GENERATOR,
155 new String[] {"column"});
156 }
157
158 /***
159 * We do not need any public constructor.
160 */
161 private SchemaUtil() {
162
163 }
164
165 /***
166 * Loads a schema from a file.
167 *
168 * @param fileName the name of the file which contains schema definition.
169 * @param log logger
170 *
171 * @return the schema
172 *
173 * @throws Exception if schema cannot be loaded.
174 */
175 public static final Schema loadSchema(final String fileName,
176 final Log log) throws Exception {
177 return loadSchema(fileName, log, null);
178 }
179
180 public static final Schema loadSchema(final String fileName,
181 final Log log, final ClassLoader classloader)
182 throws Exception {
183
184 File f = new File(fileName);
185 FileInputStream fis = new FileInputStream(f);
186 Schema schema = loadSchema(fis, log, classloader);
187
188 String homePath = f.getParent();
189 schema.setHome(homePath);
190 return schema;
191 }
192
193 /***
194 * Loads a schema from an url.
195 *
196 * @param url url
197 * @param log logger
198 *
199 * @return the schema
200 *
201 * @throws Exception on errors
202 */
203 public static final Schema loadSchema(final URL url, final Log log)
204 throws Exception {
205
206 return loadSchema(url.openStream(), log);
207 }
208
209 /***
210 * Loads a schema from an input stream.
211 *
212 * @param is input stream
213 * @param log logger
214 *
215 * @return the schema
216 *
217 * @throws Exception if schema cannot be loaded.
218 */
219 public static final Schema loadSchema(final InputStream is, final Log log)
220 throws Exception {
221 return loadSchema(is, log, null);
222 }
223
224 public static final Schema loadSchema(final InputStream is, final Log log, final ClassLoader classloader)
225 throws Exception {
226
227 Schema schema = null;
228 ErrorHandler errorHandler = new ErrorHandler() {
229 public final void warning(final SAXParseException exception)
230 throws SAXException {
231 throw exception;
232 }
233
234 public final void error(final SAXParseException exception)
235 throws SAXException {
236 throw exception;
237 }
238
239 public final void fatalError(final SAXParseException exception)
240 throws SAXException {
241 throw exception;
242 }
243 };
244
245 URL url = SchemaUtil.class.getResource(
246 "/pl/kernelpanic/dbmonster/resources/dbmonster-schema-1.1.dtd");
247 Digester digester = new Digester();
248 if (log != null) {
249 digester.setLogger(log);
250 }
251 digester.register(DTD, url.toString());
252 if (classloader != null) {
253 digester.setClassLoader(classloader);
254 } else {
255 digester.setUseContextClassLoader(true);
256 }
257
258 digester.setErrorHandler(errorHandler);
259 digester.setValidating(true);
260
261 digester.addObjectCreate("dbmonster-schema", Schema.class);
262 digester.addCallMethod("dbmonster-schema/name", "setName", 0);
263 digester.addObjectCreate("*/table", Table.class);
264 digester.addSetProperties("*/table");
265
266 digester.addObjectCreate("*/table/key", Key.class);
267 digester.addSetProperties("*/table/key");
268 digester.addFactoryCreate("*/table/key/generator",
269 new KeyGeneratorFactory());
270 digester.addSetProperty("*/table/key/generator/property",
271 "name", "value");
272 digester.addSetNext("*/table/key/generator",
273 "setGenerator", KeyGenerator.class.getName());
274 digester.addSetNext("*/table/key", "setKey", Key.class.getName());
275 digester.addSetNext("*/table", "addTable", Table.class.getName());
276 digester.addObjectCreate("*/table/column", Column.class);
277 digester.addSetProperties("*/table/column");
278 digester.addSetNext("*/table/column", "addColumn",
279 Column.class.getName());
280 digester.addFactoryCreate("*/table/column/generator",
281 new GeneratorFactory());
282 digester.addSetProperties("*/table/column/generator");
283 digester.addSetNext("*/table/column/generator", "setGenerator",
284 DataGenerator.class.getName());
285 digester.addSetProperty("*/table/column/generator/property",
286 "name", "value");
287
288 schema = (Schema) digester.parse(is);
289
290 return schema;
291 }
292 /***
293 * Validate schema.
294 *
295 * @param schema schema to validate
296 *
297 * @return list of error messages or <code>null</code> if schema is ok
298 */
299 public static final List validateSchema(final Schema schema) {
300 List errors = new ArrayList();
301 String name = schema.getName();
302 if (name == null || "".equals(name)) {
303 errors.add("Schema has no name.");
304 }
305 List tables = schema.getTables();
306 if (tables.isEmpty()) {
307 errors.add("Schema " + name + " must have at least one table.");
308 }
309 for (int i = 0; i < tables.size(); i++) {
310 Table t = (Table) tables.get(i);
311 List tableErrors = validateTable(t);
312 if (tableErrors != null) {
313 errors.addAll(tableErrors);
314 }
315 }
316 if (errors.isEmpty()) {
317 return null;
318 }
319 return errors;
320 }
321
322 /***
323 * Validates table.
324 *
325 * @param table table to validate
326 *
327 * @return list of error messages or null if table is OK.
328 */
329 public static final List validateTable(final Table table) {
330 List errors = new ArrayList();
331 String name = table.getName();
332 if (name == null || "".equals(name)) {
333 errors.add("Table has no name!");
334 }
335 if (table.getKey() == null && table.getColumns().isEmpty()) {
336 errors.add(
337 "Table " + name + " must have a key or at least one column.");
338 }
339 Key key = table.getKey();
340 if (key != null) {
341 List keyErrors = validateKey(key);
342 if (keyErrors != null) {
343 errors.addAll(keyErrors);
344 }
345 }
346 List columns = table.getColumns();
347 for (int i = 0; i < columns.size(); i++) {
348 Column c = (Column) columns.get(i);
349 List columnErrors = validateColumn(c);
350 if (columnErrors != null) {
351 errors.addAll(columnErrors);
352 }
353 }
354 if (errors.isEmpty()) {
355 return null;
356 }
357 return errors;
358 }
359 /***
360 * Validates key.
361 *
362 * @param key key to validate
363 *
364 * @return list of error messages or <code>null</code> is key is ok
365 */
366 public static final List validateKey(final Key key) {
367 List errors = new ArrayList();
368 if (key.getGenerator() == null) {
369 errors.add(
370 "Primary key for table " + key.getTable().getName()
371 + " has no generator");
372 }
373 if (errors.isEmpty()) {
374 return null;
375 }
376 return errors;
377 }
378
379 /***
380 * Validates the column.
381 *
382 * @param column column to validate
383 *
384 * @return list of error messages or <code>null</code> if column is ok
385 */
386 public static final List validateColumn(final Column column) {
387 List errors = new ArrayList();
388 String name = column.getName();
389 if (name == null || "".equals(name)) {
390 errors.add("One column in table " + column.getTable().getName()
391 + " has no name.");
392 }
393 if (column.getGenerator() == null) {
394 errors.add("Column " + name + " in table "
395 + column.getTable().getName() + " has no generator.");
396 }
397 if (errors.isEmpty()) {
398 return null;
399 }
400 return errors;
401 }
402
403 /***
404 * Returns object's properties. A property is the one which has
405 * a public getter and setter and is not reserved DBMonster's property.
406 *
407 * @param object an schema element
408 * @return list of properties
409 */
410 public static final List getProperties(final Object object) {
411 List retList = new ArrayList();
412 try {
413 Class clazz = object.getClass();
414 Method[] methods = clazz.getMethods();
415 Map map = new HashMap();
416
417 for (int i = 0; i < methods.length; i++) {
418 Method m = methods[i];
419 String mName = m.getName();
420 if (mName.startsWith("get") || mName.startsWith("set")) {
421 if (Modifier.isPublic(m.getModifiers())) {
422 map.put(mName, mName);
423 }
424 }
425 }
426
427 Iterator it = map.keySet().iterator();
428 while (it.hasNext()) {
429 String name = (String) it.next();
430 if (name.startsWith("get")) {
431 String getter = name;
432 String setter = "s" + getter.substring(1);
433 String method = getter.substring(3);
434 char ch = method.charAt(0);
435 method = Character.toLowerCase(ch) + method.substring(1);
436 if (map.containsKey(setter)) {
437 if (!SchemaUtil.isHidden(object, method)) {
438 retList.add(method);
439 }
440 }
441 }
442 }
443 } catch (Exception e) {
444 System.err.println(e.getMessage());
445 }
446 Collections.sort(retList);
447 return retList;
448 }
449
450 /***
451 * Checks if property is public and not excluded.
452 *
453 * @param object an object to check
454 * @param name property name
455 * @return <code>true</code> if property is hidden
456 */
457 public static final boolean isHidden(
458 final Object object, final String name) {
459
460 String[] excl = null;
461 if (object instanceof Table) {
462 excl = (String[]) exclusions.get(SchemaUtil.TABLE);
463 } else if (object instanceof Key) {
464 excl = (String[]) exclusions.get(SchemaUtil.KEY);
465 } else if (object instanceof KeyGenerator) {
466 excl = (String[]) exclusions.get(SchemaUtil.KEY_GENERATOR);
467 } else if (object instanceof Column) {
468 excl = (String[]) exclusions.get(SchemaUtil.COLUMN);
469 } else {
470 excl = (String[]) exclusions.get(SchemaUtil.DATA_GENERATOR);
471 }
472
473 for (int i = 0; i < excl.length; i++) {
474 if (name.equals(excl[i])) {
475 return true;
476 }
477 }
478 return false;
479 }
480
481 /***
482 * Dumps the schema to XML file.
483 *
484 * @param writer writer we are appending to
485 * @param schema schema to dump
486 *
487 * @throws Exception on errors
488 */
489 public static final void serializeSchema(
490 final Writer writer,
491 final Schema schema) throws Exception {
492 writer.write("<?xml version=\"1.0\"?>");
493 writer.write(CRLF);
494 writer.write("<!DOCTYPE dbmonster-schema PUBLIC \"");
495 writer.write(DTD);
496 writer.write("\" \"");
497 writer.write(DTD_URL);
498 writer.write("\">");
499 writer.write(CRLF);
500 writer.write("<dbmonster-schema>");
501 writer.write(CRLF);
502 writer.write(" <name>");
503 writer.write(schema.getName());
504 writer.write("</name>");
505 writer.write(CRLF);
506 Iterator it = schema.getTables().iterator();
507 while (it.hasNext()) {
508 Table table = (Table) it.next();
509 serializeTable(writer, table);
510 }
511 writer.write("</dbmonster-schema>");
512 writer.write(CRLF);
513 writer.flush();
514 }
515
516 /***
517 * Dumps a table to XML representation.
518 *
519 * @param writer writer we are appengind to
520 * @param table table to serialize
521 *
522 * @throws Exception on errors
523 */
524 public static final void serializeTable(
525 final Writer writer,
526 final Table table) throws Exception {
527 writer.write(" <table name=\"");
528 writer.write(table.getName());
529 writer.write("\" rows=\"");
530 writer.write(String.valueOf(table.getRows()));
531 writer.write("\">");
532 writer.write(CRLF);
533 if (table.getKey() != null) {
534 serializeKey(writer, table.getKey());
535 }
536 Iterator it = table.getColumns().iterator();
537 while (it.hasNext()) {
538 Column column = (Column) it.next();
539 serializeColumn(writer, column);
540 }
541 writer.write(" </table>");
542 writer.write(CRLF);
543 }
544
545 /***
546 * Dumps a key to XML representation.
547 *
548 * @param writer writer we are appending to
549 * @param key key to dump
550 *
551 * @throws Exception on errors
552 */
553 public static final void serializeKey(
554 final Writer writer,
555 final Key key) throws Exception {
556 writer.write(" <key databaseDefault=\"");
557 writer.write(String.valueOf(key.getDatabaseDefault()));
558 writer.write("\">");
559 writer.write(CRLF);
560 serializeGenerator(writer, key.getGenerator());
561 writer.write(" </key>");
562 writer.write(CRLF);
563 }
564
565 /***
566 * Dumps a column to XML.
567 *
568 * @param writer writer we are appending to
569 * @param column column to dump
570 *
571 * @throws Exception on errors
572 */
573 public static final void serializeColumn(
574 final Writer writer,
575 final Column column) throws Exception {
576 writer.write(" <column name=\"");
577 writer.write(column.getName());
578 writer.write("\" databaseDefault=\"");
579 writer.write(String.valueOf(column.getDatabaseDefault()));
580 writer.write("\">");
581 writer.write(CRLF);
582 serializeGenerator(writer, column.getGenerator());
583 writer.write(" </column>");
584 writer.write(CRLF);
585 }
586
587 /***
588 * Dumps a generator to XML.
589 *
590 * @param writer writter we are appending to
591 * @param generator generator
592 *
593 * @throws Exception on errors
594 */
595 public static final void serializeGenerator(
596 final Writer writer,
597 final Object generator) throws Exception {
598 writer.write(" <generator type=\"");
599 writer.write(generator.getClass().getName());
600 writer.write("\">");
601 writer.write(CRLF);
602 List properties = getProperties(generator);
603 Iterator it = properties.iterator();
604 while (it.hasNext()) {
605 String property = (String) it.next();
606 writer.write(" <property name=\"");
607 writer.write(property);
608 writer.write("\" value=\"");
609 String value = BeanUtils.getProperty(generator, property);
610 if (value != null) {
611 writer.write(value);
612 }
613 writer.write("\"/>");
614 writer.write(CRLF);
615 }
616 writer.write(" </generator>");
617 writer.write(CRLF);
618 }
619 }
620
621 /***
622 * Data generators factory.
623 */
624 final class GeneratorFactory extends AbstractObjectCreationFactory {
625 /***
626 * This method creates a data generator object.
627 *
628 * @param attributes SAX attributes
629 *
630 * @return created data generator
631 *
632 * @throws Exception if generator cannot be created
633 */
634 public final Object createObject(final Attributes attributes)
635 throws Exception {
636
637 String className = attributes.getValue("type");
638 Class clazz = Class.forName(className);
639 Object o = clazz.newInstance();
640 return o;
641 }
642 }
643
644 /***
645 * Key generators factory.
646 */
647 final class KeyGeneratorFactory extends AbstractObjectCreationFactory {
648 /***
649 * This method creates a key generator object.
650 *
651 * @param attributes SAX attributes
652 *
653 * @return created key generator
654 *
655 * @throws Exception if generator cannot be created
656 */
657 public final Object createObject(final Attributes attributes)
658 throws Exception {
659
660 String className = attributes.getValue("type");
661 Class clazz = Class.forName(className);
662 Object o = clazz.newInstance();
663 return o;
664 }
665 }