1 | package org.sqlorm.metadatadumper; |
2 | |
3 | import java.io.File; |
4 | import java.io.FileNotFoundException; |
5 | import java.io.FileOutputStream; |
6 | import java.io.PrintWriter; |
7 | import java.sql.Connection; |
8 | import java.sql.DriverManager; |
9 | import java.sql.ResultSet; |
10 | import java.sql.SQLException; |
11 | import java.util.ArrayList; |
12 | import java.util.Arrays; |
13 | import java.util.Date; |
14 | import java.util.HashMap; |
15 | import java.util.Iterator; |
16 | import java.util.Map; |
17 | import java.util.Properties; |
18 | import java.util.TreeMap; |
19 | |
20 | /** |
21 | * Class to read meta data from a database and create classes containing constants based upon that meta data. |
22 | * <p> |
23 | * This class is written in Java5 style, but can easily be translated to Java 4 style. |
24 | * |
25 | * @author Kasper B. Graversen, (c) 2007-2008 |
26 | */ |
27 | public class ConstantsDump { |
28 | private static final String CLASS_HEADER = "\n" // |
29 | + "/**\n" // |
30 | + " * This class is auto generated by SQL ORM.\n" // |
31 | + " * Generated: " + new Date() + "\n" // |
32 | + " * \n" // |
33 | + " * DO NOT EDIT UNLESS WHILST REFACTORING!!\n" // |
34 | + " */"; |
35 | private final String IND = "\t\t"; |
36 | private final String INDD = "\t\t\t"; |
37 | |
38 | static boolean verbose = false; |
39 | ConnectionInfo connInfo = new ConnectionInfo(); |
40 | FormatInfo formatInfo = new FormatInfo(); |
41 | |
42 | public ConstantsDump(ConnectionInfo connInfo, FormatInfo formatInfo) throws Exception { |
43 | this.connInfo = connInfo; |
44 | this.formatInfo = formatInfo; |
45 | } |
46 | |
47 | public ConstantsDump() throws Exception { |
48 | } |
49 | |
50 | private static PrintWriter createFileHandle(String outputDir, String outputFileName) throws FileNotFoundException { |
51 | ensureOutputDirExistence(outputDir); |
52 | File file = new File(outputDir, outputFileName); |
53 | log("Creating file '%s'", file.toString()); |
54 | PrintWriter out = new PrintWriter(new FileOutputStream(file)); |
55 | return out; |
56 | } |
57 | |
58 | private static void ensureOutputDirExistence(String outputDir) { |
59 | File dir = new File(outputDir); |
60 | if(dir.exists()) { |
61 | return; |
62 | } |
63 | log("'%s' non-existing, trying to create it.", outputDir); |
64 | if(dir.mkdirs() == false) { |
65 | throw new RuntimeException("Cannot create the dir '" + outputDir + "'"); |
66 | } |
67 | } |
68 | |
69 | private void processSchema(String schema, HashMap<String, HashMap<String, Object>> metaData) |
70 | throws FileNotFoundException { |
71 | schema = schema.substring(0, 1).toUpperCase() + schema.substring(1); |
72 | final String CLASS_NAME = formatInfo.getClassNamePrefix() + schema + "MetaData"; |
73 | final String outputFileName = CLASS_NAME + ".java"; |
74 | final PrintWriter out = createFileHandle(formatInfo.getOutputDir() + formatInfo.getPackageName().replace('.', '/'), |
75 | outputFileName); |
76 | log("package '%s'", formatInfo.getPackageName()); |
77 | out.println("package " + formatInfo.getPackageName() + ";"); |
78 | out.println(CLASS_HEADER); |
79 | out.println("public class " + CLASS_NAME + " {"); |
80 | |
81 | // put the definitions of the column name classes at they are not used when refactoring |
82 | ArrayList<String> ColumnNameClasses = new ArrayList<String>(); |
83 | |
84 | // sort tables in a schema alpha asc. |
85 | Map<String, HashMap<String, Object>> sortedMetaData = new TreeMap<String, HashMap<String, Object>>(metaData); |
86 | |
87 | for(Iterator<String> dbTables = sortedMetaData.keySet().iterator(); dbTables.hasNext();) { |
88 | String tableName = dbTables.next(); |
89 | log("table '%s'", tableName); |
90 | |
91 | // generate method name class for table constants |
92 | final String TableNameClassName = schema + tableName.toUpperCase() + "_TABLE"; |
93 | |
94 | // generate table constant |
95 | out.println(generateStaticConstant(TableNameClassName, tableName.toUpperCase(), tableName, "\t")); |
96 | |
97 | // generate column name inner classes |
98 | final String COLUMN_CLASS = tableName.toUpperCase() + "_COL"; |
99 | ColumnNameClasses.add("\tpublic static class " + COLUMN_CLASS |
100 | + " implements org.sqlorm.metadatadumper.IColumnName {\n" // |
101 | + IND + "private String tableName, columnName;\n" // |
102 | + IND + "private " + COLUMN_CLASS + "(String tableName, String columnName) { \n" // |
103 | + INDD + "this.tableName = tableName;\n" // |
104 | + INDD + "this.columnName = columnName;\n" // |
105 | + IND + "}\n" // |
106 | + IND + "public String _() { return columnName; }\n" // |
107 | + IND + "public String at(String tablePrefix) { return tablePrefix+\".\"+columnName; }\n" // |
108 | + IND + "public String toString() { return tableName +\".\"+columnName; }\n" // |
109 | + "\t}\n"); |
110 | |
111 | // generate table name class containing all the fields |
112 | out.println("\tpublic static class " + TableNameClassName |
113 | + " implements org.sqlorm.metadatadumper.ITableName {"); |
114 | |
115 | // generate column constants |
116 | out.println(generateColumnConstant(COLUMN_CLASS, "_STAR", tableName, "*", IND)); |
117 | Map<String, Object> sortedColumnsMetaData = new TreeMap<String, Object>(metaData.get(tableName)); |
118 | for(Iterator<String> dbColumns = sortedColumnsMetaData.keySet().iterator(); dbColumns.hasNext();) { |
119 | String columnName = dbColumns.next(); |
120 | log(" -> column '%s'", columnName); |
121 | |
122 | out.println(generateColumnConstant(COLUMN_CLASS, columnName.toUpperCase(), tableName, columnName, IND)); |
123 | } |
124 | // generate end of class and its functionality |
125 | out.println(IND + "// implementation\n" // |
126 | + IND + "private String tableName;\n" // |
127 | + IND + "private " + TableNameClassName + "(String tableName) { this.tableName = tableName; }\n" // |
128 | + IND + "public String toString() { return tableName; }"); |
129 | out.println("\t}"); |
130 | out.println(""); |
131 | out.println(""); |
132 | } |
133 | |
134 | // generate all the column names |
135 | |
136 | out.println("\n\n\t // uninteresting stuff below... \n\n\n\n\n\n\n\n\n\n"); |
137 | for(String columnClassName : ColumnNameClasses) { |
138 | out.println(columnClassName); |
139 | } |
140 | ColumnNameClasses.clear(); |
141 | out.print("}"); |
142 | out.close(); |
143 | } |
144 | |
145 | private static String generateStaticConstant(String type, String name, String value, String tabs) { |
146 | return String.format("%spublic static final %s %s = new %s(\"%s\");",// |
147 | tabs, type, name, type, value); |
148 | } |
149 | |
150 | private static String generateColumnConstant(String type, String name, String tableArg, String columnArg, String tabs) { |
151 | return String.format("%spublic final %s %-15s = new %s(\"%s\",\"%s\");",// |
152 | tabs, type, name, type, tableArg, columnArg); |
153 | } |
154 | |
155 | // old way.. generating javadoc as well.. but finding this too verbose to read..with little added value |
156 | // private static String generateColumnConstant(String type, String name, String tableArg, String columnArg, String |
157 | // tabs) { |
158 | // return String.format("%s/** constant = '%s' */\n" // |
159 | // + "%spublic final %s %s = new %s(\"%s\",\"%s\");\n",// |
160 | // tabs, columnArg,// |
161 | // tabs, type, name, type, tableArg, columnArg); |
162 | // } |
163 | |
164 | /** |
165 | * get meta data for all schemas |
166 | */ |
167 | private ThreeDHashMap<String, String, String, Object> getMetaData(ConnectionInfo connInfo, Connection connection) |
168 | throws Exception, SQLException { |
169 | ThreeDHashMap<String, String, String, Object> metaInfo = new ThreeDHashMap<String, String, String, Object>(); |
170 | |
171 | for(String schema : connInfo.getSchemaNames()) { |
172 | log("Processing schema '%s' accepted", schema); |
173 | connection = reconnect(schema, connection); |
174 | ResultSet rs = connection.getMetaData().getColumns(schema, null, "%", "%"); |
175 | while(rs.next()) { |
176 | // String schemaname = rs.getString(1); |
177 | String tablename = rs.getString(3); |
178 | String columnname = rs.getString(4); |
179 | metaInfo.set(schema, tablename, columnname, null); |
180 | } |
181 | } |
182 | return metaInfo; |
183 | } |
184 | |
185 | private Connection connect() throws InstantiationException, IllegalAccessException, ClassNotFoundException, |
186 | SQLException { |
187 | Class.forName(connInfo.getDriverClass()).newInstance(); |
188 | |
189 | final Properties dbConnection = new Properties(); |
190 | dbConnection.put("user", connInfo.getUserName()); |
191 | dbConnection.put("password", connInfo.getPassword()); |
192 | final String url = connInfo.getUrl() + "/" + connInfo.getSchemaNames().get(0); |
193 | log("Connecting to '%s' as %s:%s", url, connInfo.getUserName(), connInfo.getPassword()); |
194 | |
195 | return DriverManager.getConnection(url, dbConnection); |
196 | // old way return DriverManager.getConnection(url, connInfo.getUserName(), connInfo.getPassword()); |
197 | } |
198 | |
199 | private static void log(String message, Object... args) { |
200 | if(verbose) { |
201 | System.out.println(String.format(message, args)); |
202 | } |
203 | } |
204 | |
205 | /** |
206 | * for each schema we need to re-connect to the database in order to properly read the meta data |
207 | */ |
208 | private Connection reconnect(String schema, Connection con) throws Exception { |
209 | con.close(); |
210 | return DriverManager |
211 | .getConnection(connInfo.getUrl() + "/" + schema, connInfo.getUserName(), connInfo.getPassword()); |
212 | } |
213 | |
214 | public static void main(String[] args) throws Exception { |
215 | try { |
216 | ConstantsDump dumper = new ConstantsDump(); |
217 | dumper.parseArguments(args); |
218 | dumper.dumpMetaData(); |
219 | } |
220 | catch(Exception e) { |
221 | System.err.println("Error!"); |
222 | System.err.println(e.toString() + "\n-----------------------------"); |
223 | System.err.println("The following arguments: "); |
224 | for(String arg : args) { |
225 | System.err.print("'" + arg + "' "); |
226 | } |
227 | System.err.println("\n\nApplication arguments are\n"); |
228 | System.err |
229 | .println(" -driver xxx Java.class.driver (e.g. com.mysql.jdbc.Driver)\n"// |
230 | + " -url xxx Url to db e.g. jdbc:mysql://127.0.0.1:3306\n"// |
231 | + " -user xxx Db user, e.g. scott\n" // |
232 | + " -password xxx Db password, e.g. tiger\n" // |
233 | + " -schema xxx,xxx,... Db schema.. for several schemas separate using ',' and no spaces!\n"// |
234 | + " -outputdir xxx Destination folder to place the generated files (not the top src folder!). \n" // |
235 | + " -javapackage xxx Destination package for the generated classes (must match the '-outputdir')\n"// |
236 | + " -verbose (optional) Output much more info\n" // |
237 | + " -overwrite (optional) Overwrite generated classes (false pr. default)\n"); |
238 | throw e; |
239 | } |
240 | } |
241 | |
242 | public void parseArguments(String... args) { |
243 | for(int i = 0; i < args.length; i++) { |
244 | // control info |
245 | if(args[i].equals("-driver")) { |
246 | connInfo.setDriverClass(args[++i]); |
247 | } |
248 | if(args[i].equals("-password")) { |
249 | connInfo.setPassword(args[++i]); |
250 | } |
251 | if(args[i].equals("-user")) { |
252 | connInfo.setUserName(args[++i]); |
253 | } |
254 | if(args[i].equals("-url")) { |
255 | connInfo.setUrl(args[++i]); |
256 | } |
257 | |
258 | if(args[i].equals("-schema")) { |
259 | connInfo.getSchemaNames().addAll(Arrays.asList(args[++i].split(","))); |
260 | } |
261 | // formatter settings |
262 | if(args[i].equals("-outputdir")) { |
263 | formatInfo.setOutputDir(args[++i]); |
264 | } |
265 | if(args[i].equals("-javapackage")) { |
266 | formatInfo.setPackageName(args[++i]); |
267 | } |
268 | |
269 | // other |
270 | if(args[i].equals("-verbose")) { |
271 | verbose = true; |
272 | } |
273 | if(args[i].equals("-overwrite")) { |
274 | formatInfo.setOverwrite(true); |
275 | } |
276 | } |
277 | } |
278 | |
279 | /** |
280 | * When calling this method, you must have set the connection info and the format info or call |
281 | * <code>parseArguments()</code>. |
282 | * |
283 | * @see parseArguments(String...) |
284 | */ |
285 | public void dumpMetaData() throws Exception { |
286 | connInfo.validate(); |
287 | formatInfo.validate(); |
288 | Connection connection = connect(); |
289 | // schema, table, column -> null |
290 | ThreeDHashMap<String, String, String, Object> metaData = getMetaData(connInfo, connection); |
291 | |
292 | for(String schema : connInfo.getSchemaNames()) { |
293 | processSchema(schema, metaData.get(schema)); |
294 | } |
295 | } |
296 | } |