root/trunk/src/java/org/jcoderz/testdata/Importer.java

Revision 1157, 28.0 kB (checked in by dcoppola, 4 years ago)

merged changes from upstream

Line 
1package org.jcoderz.testdata;
2
3import java.io.File;
4import java.io.IOException;
5import java.io.InputStream;
6import java.sql.Connection;
7import java.sql.DriverManager;
8import java.sql.ResultSet;
9import java.sql.SQLException;
10import java.sql.SQLWarning;
11import java.sql.Statement;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.Iterator;
17import java.util.List;
18import java.util.Locale;
19import java.util.Map;
20import java.util.Properties;
21import java.util.Set;
22import java.util.logging.Level;
23import java.util.logging.Logger;
24
25import nu.xom.Builder;
26import nu.xom.Document;
27import nu.xom.Element;
28import nu.xom.Node;
29import nu.xom.Nodes;
30import nu.xom.XPathContext;
31import nu.xom.xinclude.XIncluder;
32
33import org.hibernate.SessionFactory;
34import org.hibernate.cfg.Configuration;
35import org.hibernate.mapping.Table;
36
37/**
38 * Generic Test data importer. Recursively scans a directory for test data items and
39 * submits them via JDBC to a predefined database. The tables and attribute names are
40 * mapped straightforward to the hibernate configuration expected on the classpath. This
41 * implementation relies on auto index of all tables.
42 *
43 * @author Torsten Stolpmann
44 */
45public class Importer {
46
47    private static final int ERROR_EXIT_CODE = 20;
48
49    private static final String DEFAULT_TABLE_PREFIX = "S0IR_";
50
51    private static final long DEFAULT_SEQUENCE_BASE = 100000L;
52
53    private static final String SUFFIX_XML = ".xml";
54
55    private static final String FILE_SEPARATOR = "/";
56
57    private static final XPathContext TD_CONTEXT = new XPathContext("td",
58            "http://jcoderz.org/test-data");
59
60    static final Logger logger = Logger.getLogger(Importer.class.getName());
61
62    private String tablePrefix;
63    private long sequenceBase;
64
65    private Connection connection;
66
67    /**
68     * The main method.
69     *
70     * @param args Command line arguments.
71     */
72    public static void main(String[] args) {
73
74        if (args.length < 1) {
75            System.out.println("Usage: " + Importer.class.getName()
76                    + " <sourceDirectory>");
77        }
78
79        String sourceDirectory = args[0];
80
81        Properties properties = new Properties();
82        List<String> tableNames = new ArrayList<String>();
83
84        logger.info("Starting import ...");
85
86        Locale.setDefault(Locale.GERMANY);
87
88        try {
89            String defaultPropertiesName = "/testdata.properties";
90
91            InputStream defaultInput = Importer.class
92                    .getResourceAsStream(defaultPropertiesName);
93            if (defaultInput != null) {
94                logger.fine("Loading default properties (" + defaultPropertiesName + ")");
95                properties.load(defaultInput); //$NON-NLS-1$
96            } else {
97                logger.fine("Default properties not found");
98            }
99            String userPropertiesName = FILE_SEPARATOR + System.getProperty("user.name")
100                    + ".properties";
101            InputStream userInput = Importer.class
102                    .getResourceAsStream(userPropertiesName);
103            if (userInput != null) {
104                logger.fine("Loading user properties");
105                properties.load(userInput); //$NON-NLS-1$
106            } else {
107                logger.fine("User properties (" + userPropertiesName + ") not found");
108            }
109            System.getProperties().putAll(properties);
110            logger.warning(System.getProperties().toString());
111            properties = System.getProperties();
112
113            Configuration hibernateConfig = new Configuration();
114            hibernateConfig.addProperties(properties);
115            hibernateConfig.configure();
116            hibernateConfig.getProperties().remove("hibernate.connection.datasource");
117            hibernateConfig.getProperties().remove(
118                    "hibernate.transaction.manager_lookup_class");
119            hibernateConfig.getProperties().remove("hibernate.transaction.factory_class");
120
121            Iterator<?> iter = hibernateConfig.getTableMappings();
122            while (iter.hasNext()) {
123                Table table = (Table) iter.next();
124                tableNames.add(table.getName().toUpperCase());
125            }
126            SessionFactory factory = hibernateConfig.buildSessionFactory();
127        } catch (IOException e) {
128            logger.log(Level.SEVERE, "Exception: ", e);
129            System.exit(ERROR_EXIT_CODE);
130        }
131
132        Importer importer = new Importer(DEFAULT_TABLE_PREFIX, DEFAULT_SEQUENCE_BASE);
133
134        Map<String, Document> items = new HashMap<String, Document>();
135
136        if (!importer.loadItems(sourceDirectory, items)) {
137            logger.log(Level.SEVERE, "Invalid test data set detected - aborting.");
138            System.exit(ERROR_EXIT_CODE);
139        }
140
141        Map<String, Set<String>> dependencyMap = importer.buildDependencyMap(items);
142
143        if (importer.validateDependencies(items, dependencyMap)) {
144            List<String> result = new ArrayList<String>();
145            List<String> leftover = new ArrayList<String>();
146
147            importer.reOrderItems(items, dependencyMap, result, leftover);
148
149            List<String> queries = new ArrayList<String>();
150            if (importer.generateQueries(result, items, queries, null, tableNames, properties,
151                    false)) {
152                logger.info("Execution plan for constrained inserts:");
153                logger.info("---------------------------------------");
154                for (String query : queries) {
155                    logger.info(query);
156                }
157                logger.info("---------------------------------------");
158
159            } else {
160                logger
161                        .severe("No insert statements have been executed since errors have been detected.");
162                System.exit(ERROR_EXIT_CODE);
163            }
164
165            List<String> leftOverQueries = new ArrayList<String>();
166            Set<String> postQueries = new HashSet<String>();
167
168            if (importer.generateQueries(leftover, items, leftOverQueries, postQueries, tableNames,
169                    properties, true)) {
170                logger.info("Execution plan for unconstrained inserts:");
171                logger.info("-----------------------------------------");
172                for (String query : leftOverQueries) {
173                    logger.info(query);
174                }
175
176                for (String query : postQueries) {
177                    logger.info(query);
178                }
179                logger.info("---------------------------------------");
180
181            } else {
182                logger
183                        .severe("No insert statements have been executed since errors have been detected.");
184                System.exit(ERROR_EXIT_CODE);
185            }
186
187            importer.executeQueries(properties, queries, false);
188            importer.executeQueries(properties, leftOverQueries, false);
189            importer.executeQueries(properties, postQueries, true);
190
191            try {
192                Connection conn = importer.getConnection(properties);
193                conn.close();
194            } catch (ClassNotFoundException e) {
195                logger.log(Level.SEVERE, "Exception: ", e);
196            } catch (SQLException e) {
197                logger.log(Level.SEVERE, "Exception: ", e);
198            }
199        } else {
200            logger
201                    .severe("No insert statements have been executed since errors have been detected.");
202            System.exit(ERROR_EXIT_CODE);
203        }
204    }
205
206    public Importer(final String prefix, final long base) {
207
208        tablePrefix = prefix;
209        sequenceBase = base;
210
211    }
212
213    /**
214     * Execute the supplied SQL queries.
215     *
216     * @param hibernateProperties the hibernate properties.
217     * @param queries the queries
218     */
219    private void executeQueries(final Properties hibernateProperties,
220            final Collection<String> queries, boolean delayErrors) {
221
222        boolean success = true;
223        try {
224            Connection conn = getConnection(hibernateProperties);
225
226            for (SQLWarning warn = conn.getWarnings(); warn != null; warn = warn
227                    .getNextWarning()) {
228                logger.warning("SQL Warning:");
229                logger.warning("State  : " + warn.getSQLState());
230                logger.warning("Message: " + warn.getMessage());
231                logger.warning("Error  : " + warn.getErrorCode());
232            }
233
234            Statement langStmt = conn.createStatement();
235            String alterStatement = "ALTER SESSION SET NLS_TERRITORY='AMERICA'";
236            langStmt.execute(alterStatement);
237            langStmt.close();
238
239            for (String query : queries) {
240
241                try {
242                    logger.info(query);
243                    Statement stmt = conn.createStatement();
244                    ResultSet rs = stmt.executeQuery(query);
245
246                    rs.close();
247                    stmt.close();
248                    conn.commit();
249                } catch (SQLException e) {
250
251                    logger.severe("SQL Exception:");
252
253                    while (e != null) {
254                        logger.severe("State  : " + e.getSQLState());
255                        logger.severe("Message: " + e.getMessage());
256                        logger.severe("Error  : " + e.getErrorCode());
257
258                        e = e.getNextException();
259                    }
260                    if (!delayErrors) {
261                        System.exit(ERROR_EXIT_CODE);
262                    }
263                    success = false;
264                }
265            }
266        } catch (SQLException e) {
267
268            logger.severe("SQL Exception:");
269
270            while (e != null) {
271                logger.severe("State  : " + e.getSQLState());
272                logger.severe("Message: " + e.getMessage());
273                logger.severe("Error  : " + e.getErrorCode());
274
275                e = e.getNextException();
276            }
277            if (!delayErrors) {
278                System.exit(ERROR_EXIT_CODE);
279            }
280            success = false;
281
282        } catch (Exception e) {
283            logger.log(Level.SEVERE, "Exception: ", e);
284            System.exit(ERROR_EXIT_CODE);
285        }
286        if (delayErrors && !success) {
287            System.exit(ERROR_EXIT_CODE);
288        }
289    }
290
291    /**
292     * Write the map of documents.
293     *
294     * @param items the map of items
295     * @param queries the resulting queries
296     * @param postQueries the post insert queries
297     * @param idList the ordered list of item names
298     * @param tableNames the table names
299     * @param hibernateProperties the hibernate properties.
300     * @param disableConstraints true if constraints should be disabled here.
301     * @return true, if write
302     */
303    private boolean generateQueries(final List<String> idList,
304            final Map<String, Document> items, final List<String> queries,
305            final Set<String> postQueries,
306            final List<String> tableNames, final Properties hibernateProperties,
307            final boolean disableConstraints) {
308
309        boolean valid = true;
310
311        final Set<String> preQueries = new HashSet<String>();
312        final List<String> insertQueries = new ArrayList<String>();
313
314        for (String id : idList) {
315
316            StringBuffer query = new StringBuffer();
317            StringBuffer names = new StringBuffer("(");
318            StringBuffer values = new StringBuffer("(");
319
320            Document item = items.get(id);
321            String tableName = getTableName(item);
322
323            if (!tableNames.contains(tableName)) {
324                logger.severe("The type " + getItemType(item) + " of item "
325                        + getItemId(item)
326                        + " has no matching table in the current configuration ("
327                        + tableName + ")");
328                valid = false;
329            }
330            Map<String, Node> attributes = getAttributes(item);
331
332            int position = 0;
333            int end = attributes.entrySet().size();
334
335            for (Map.Entry<String, Node> attribute : attributes.entrySet()) {
336
337                position++;
338
339                String name = attribute.getKey().toUpperCase();
340                Node node = attribute.getValue();
341
342                if (node instanceof Element) {
343                    Element element = (Element) node;
344                    if (element.getLocalName().equals("value")) {
345                        names.append(name);
346                        values.append("'");
347                        values.append(element.getValue());
348                        values.append("'");
349                    } else if (element.getLocalName().equals("autovalue")) {
350                        names.append(name);
351                        values.append(getAsPrimaryKey(id));
352                    } else if (element.getLocalName().equals("ref")) {
353                        String reference = element.getValue();
354                        if (reference.length() > 0) {
355                            if (items.containsKey(reference)) {
356                                names.append(name);
357                                values.append(getAsPrimaryKey(element.getValue()));
358                            } else {
359                                logger.severe("ERROR! the reference " + reference
360                                        + " could not be resolved.");
361                                valid = false;
362                            }
363                        }
364                    }
365                }
366                if (position == end) {
367                    names.append(") ");
368                    values.append(") ");
369                } else {
370                    names.append(", ");
371                    values.append(", ");
372                }
373            }
374            if (attributes.size() > 0) {
375                query.append("INSERT INTO " + tableName);
376                query.append(" ");
377                query.append(names);
378                query.append(" VALUES ");
379                query.append(values);
380            }
381            logger.fine(query.toString());
382            if (disableConstraints) {
383                preQueries.addAll(buildEnableConstraints(false, tableName,
384                        hibernateProperties));
385                insertQueries.add(query.toString());
386                postQueries.addAll(buildEnableConstraints(true, tableName,
387                        hibernateProperties));
388            } else {
389                insertQueries.add(query.toString());
390            }
391        }
392        queries.addAll(preQueries);
393        queries.addAll(insertQueries);
394        return valid;
395    }
396
397    /**
398     * Reorder the items.
399     *
400     * @param items the document items.
401     * @param dependencyMap the dependency map
402     * @param result the resulting list of id's
403     * @param leftover the leftover items if any.
404     */
405    private void reOrderItems(final Map<String, Document> items,
406            final Map<String, Set<String>> dependencyMap, final List<String> result,
407            final List<String> leftover) {
408
409        boolean changed;
410        logger.fine("Reordering " + items.size() + " items ...");
411        final Map<String, Document> itemPool = new HashMap<String, Document>(items);
412        do {
413            changed = false;
414
415            Iterator<Map.Entry<String, Document>> entries = itemPool.entrySet()
416                    .iterator();
417            while (entries.hasNext()) {
418                Map.Entry<String, Document> item = entries.next();
419                String id = item.getKey();
420                boolean accepted = true;
421
422                Set<String> dependencies = dependencyMap.get(id);
423                for (String dependency : dependencies) {
424                    if (!result.contains(dependency)) {
425                        accepted = false;
426                        break;
427                    }
428                }
429                if (accepted) {
430                    List<String> declaredDependencies = getDeclaredDependencies(item
431                            .getValue());
432                    for (String declaredDependency : declaredDependencies) {
433                        if (!result.contains(declaredDependency)) {
434                            accepted = false;
435                            break;
436                        }
437                    }
438                }
439
440                if (accepted) {
441                    logger.fine("Accepting item: " + id);
442                    entries.remove();
443                    result.add(id);
444                    changed = true;
445                }
446            }
447        } while (changed);
448        logger.fine("Accepted " + result.size() + " items ...");
449
450        if (!itemPool.isEmpty()) {
451            logger
452                    .warning("Reference loop(s) detected! The following items will be added ignoring db-constraints:");
453            for (String id : itemPool.keySet()) {
454                leftover.add(id);
455                StringBuffer dependencyList = new StringBuffer();
456                Set<String> dependencies = dependencyMap.get(id);
457                for (String dependency : dependencies) {
458                    dependencyList.append(dependency);
459                    dependencyList.append(" ");
460                }
461
462                logger.warning(id + ": [" + dependencyList.toString() + "]");
463            }
464        }
465    }
466
467    /**
468     * Validate dependencies.
469     *
470     * @param items the items
471     * @param dependencyMap the dependency map
472     * @return true, if successful
473     */
474    private boolean validateDependencies(Map<String, Document> items,
475            Map<String, Set<String>> dependencyMap) {
476
477        boolean valid = true;
478
479        for (Map.Entry<String, Set<String>> dependenciesEntry : dependencyMap.entrySet()) {
480
481            String id = dependenciesEntry.getKey();
482            Set<String> dependencies = dependenciesEntry.getValue();
483            for (String dependency : dependencies) {
484                if (!items.containsKey(dependency)) {
485                    logger.severe("Item " + id + " contains invalid reference: "
486                            + dependency + " (Note: Self references are not allowed!)");
487                    valid = false;
488                }
489            }
490        }
491        return valid;
492    }
493
494    /**
495     * Builds the dependency map.
496     *
497     * @param items the items to build from.
498     * @return the Map of dependencies.
499     */
500    private Map<String, Set<String>> buildDependencyMap(final Map<String, Document> items) {
501
502        Map<String, Set<String>> dependencyMap = new HashMap<String, Set<String>>();
503
504        for (Map.Entry<String, Document> item : items.entrySet()) {
505
506            String id = item.getKey();
507            Document document = item.getValue();
508
509            Set<String> dependencies = new HashSet<String>();
510
511            Nodes references = document.query("/td:item/td:attribute/td:ref", TD_CONTEXT);
512
513            for (int j = 0; j < references.size(); j++) {
514                Node node = references.get(j);
515                String refId = node.getValue();
516                if (refId.length() > 0) {
517                    dependencies.add(refId);
518                } else {
519                    // Drop empty references.
520                    node.getParent().detach();
521                }
522            }
523
524            dependencyMap.put(id, dependencies);
525        }
526        return dependencyMap;
527    }
528
529    /**
530     * Recursively scan the directory and add found items to the map.
531     *
532     * @param dirName the directory name
533     * @param itemMap the item map
534     */
535    private boolean loadItems(String dirName, Map<String, Document> itemMap) {
536
537        File rootDir = new File(dirName);
538        if (!rootDir.exists()) {
539            logger.severe("Root directory '" + dirName + "' not found.");
540            return false;
541        }
542        String[] entries = rootDir.list();
543        boolean success = true;
544
545        for (int i = 0; i < entries.length; i++) {
546            String entry = dirName + FILE_SEPARATOR + entries[i];
547            File subFile = new File(entry);
548            if (subFile.isDirectory()) {
549                success &= loadItems(entry, itemMap);
550            } else {
551                if (subFile.getName().endsWith(SUFFIX_XML)) {
552                    Builder parser = new Builder(false);
553                    try {
554                        Document raw = parser.build(entry);
555                        Document document = XIncluder.resolve(raw);
556                        String id = getItemId(document);
557
558                        if (itemMap.containsKey(id)) {
559                            logger.severe("Duplicate id detected: " + id
560                                    + " - item will be skipped");
561                            success = false;
562                        } else {
563                            itemMap.put(id, document);
564                            logger.fine("Adding item: " + id);
565                        }
566                    } catch (Exception e) {
567                        logger.log(Level.SEVERE, "Error parsing file " + entry + ": ", e);
568                    }
569                }
570            }
571        }
572        return success;
573    }
574
575    /**
576     * Gets the supplied id as a primary key representation.
577     *
578     * @param id the id
579     * @return the primary key
580     */
581    protected String getAsPrimaryKey(final String id) {
582
583        return "" + (sequenceBase + Long.parseLong(id.substring(3)));
584    }
585
586    /**
587     * Returns the table name for the given item.
588     *
589     * @param item the item
590     * @return the table name
591     */
592    protected String getTableName(final Document item) {
593
594        Node type = item.query("//td:item/td:type", TD_CONTEXT).get(0);
595        return tablePrefix + type.getValue().toUpperCase();
596    }
597
598    /**
599     * Gets the item id.
600     *
601     * @param document the document containing the item.
602     * @return the item id if any, null else.
603     */
604    private String getItemType(final Document document) {
605
606        String type = null;
607
608        Nodes nodes = document.query("//td:item/td:type", TD_CONTEXT);
609        for (int j = 0; j < nodes.size(); j++) {
610            Node node = nodes.get(j);
611            type = node.getValue();
612            break;
613        }
614        if (type == null) {
615            logger.severe(getItemId(document) + " contains no type element!");
616        }
617        return type;
618    }
619
620    /**
621     * Gets the item id.
622     *
623     * @param document the document containing the item.
624     * @return the item id if any, null else.
625     */
626    private String getItemId(final Document document) {
627
628        String id = null;
629
630        Nodes nodes = document.query("//td:item/td:id", TD_CONTEXT);
631        for (int j = 0; j < nodes.size(); j++) {
632            Node node = nodes.get(j);
633            id = node.getValue();
634            break;
635        }
636        return id;
637    }
638
639    /**
640     * Return the item attributes.
641     *
642     * @param document the document containing the item.
643     * @return the attributes of this item.
644     */
645    private Map<String, Node> getAttributes(final Document document) {
646
647        Map<String, Node> attributeMap = new HashMap<String, Node>();
648
649        Nodes attributes = document.query("//td:item/td:attribute", TD_CONTEXT);
650        for (int j = 0; j < attributes.size(); j++) {
651            Node attribute = attributes.get(j);
652            String name = attribute.query("td:name", TD_CONTEXT).get(0).getValue();
653            Nodes values = attribute.query("td:value|td:autovalue|td:ref", TD_CONTEXT);
654            if (values.size() > 0) {
655                attributeMap.put(name, values.get(0));
656            }
657        }
658        return attributeMap;
659    }
660
661    /**
662     * Return the explicit item dependencies.
663     *
664     * @param document the document containing the item.
665     * @return the explicit dependencies of this item.
666     */
667    private List<String> getDeclaredDependencies(final Document document) {
668
669        List<String> result = new ArrayList<String>();
670
671        Nodes dependencies = document.query("//td:item/td:dependency", TD_CONTEXT);
672        for (int j = 0; j < dependencies.size(); j++) {
673            Node dependency = dependencies.get(j);
674            result.add(dependency.getValue());
675        }
676        return result;
677    }
678
679    /**
680     * Enable database constraints.
681     *
682     * @param enabled the enabled flag
683     * @param tableName the table name
684     * @param hibernateProperties the hibernate properties
685     * @return the list of queries
686     */
687    private List<String> buildEnableConstraints(final boolean enabled,
688            final String tableName, final Properties hibernateProperties) {
689
690        final List<String> queries = new ArrayList<String>();
691        List<String> constraints = getConstraints(tableName, hibernateProperties);
692        for (String constraint : constraints) {
693
694            final String query;
695            if (enabled) {
696                query = "ALTER TABLE " + tableName + " ENABLE CONSTRAINT " + constraint;
697            } else {
698                query = "ALTER TABLE " + tableName + " DISABLE CONSTRAINT " + constraint;
699            }
700            queries.add(query);
701        }
702        return queries;
703    }
704
705    private List<String> getConstraints(String tableName, Properties hibernateProperties) {
706
707        final List<String> constraints = new ArrayList<String>();
708        try {
709            Connection conn = getConnection(hibernateProperties);
710
711            for (SQLWarning warn = conn.getWarnings(); warn != null; warn = warn
712                    .getNextWarning()) {
713                logger.warning("SQL Warning:");
714                logger.warning("State  : " + warn.getSQLState());
715                logger.warning("Message: " + warn.getMessage());
716                logger.warning("Error  : " + warn.getErrorCode());
717            }
718
719            String query = "select constraint_name from user_constraints where table_name ='"
720                    + tableName + "' and constraint_type='R'";
721
722            logger.info(query);
723            Statement stmt = conn.createStatement();
724            ResultSet rs = stmt.executeQuery(query);
725            while (rs.next()) {
726                String constraint = rs.getString(1);
727                constraints.add(constraint);
728            }
729            rs.close();
730            stmt.close();
731        } catch (SQLException e) {
732
733            logger.severe("SQL Exception:");
734
735            while (e != null) {
736                logger.severe("State  : " + e.getSQLState());
737                logger.severe("Message: " + e.getMessage());
738                logger.severe("Error  : " + e.getErrorCode());
739
740                e = e.getNextException();
741            }
742        } catch (Exception e) {
743            logger.log(Level.SEVERE, "Exception: ", e);
744        }
745        return constraints;
746    }
747
748    /**
749     * Gets the database connection for the given properties.
750     *
751     * @param hibernateProperties the hibernate properties
752     * @return the connection
753     * @throws ClassNotFoundException the class not found exception
754     * @throws SQLException the SQL exception
755     */
756    private Connection getConnection(final Properties hibernateProperties)
757            throws ClassNotFoundException, SQLException {
758
759        if (connection == null) {
760            Class.forName(hibernateProperties
761                    .getProperty("hibernate.connection.driver_class"));
762
763            Properties info = new Properties();
764            info.setProperty("user", hibernateProperties
765                    .getProperty("hibernate.connection.username"));
766            info.setProperty("password", hibernateProperties
767                    .getProperty("hibernate.connection.password"));
768            info.setProperty("useUnicode", "true");
769            info.setProperty("characterEncoding", "UTF-8");
770
771            connection = DriverManager.getConnection(hibernateProperties
772                    .getProperty("hibernate.connection.url"), info);
773        }
774        return connection;
775    }
776}
Note: See TracBrowser for help on using the browser.