001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. 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: package org.apache.jetspeed.serializer;
018:
019: import java.io.BufferedWriter;
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FileWriter;
023: import java.io.FilenameFilter;
024: import java.io.IOException;
025: import java.util.ArrayList;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import javax.xml.transform.Result;
030: import javax.xml.transform.Source;
031: import javax.xml.transform.Transformer;
032: import javax.xml.transform.TransformerFactory;
033: import javax.xml.transform.stream.StreamResult;
034: import javax.xml.transform.stream.StreamSource;
035:
036: import javolution.xml.XMLBinding;
037: import javolution.xml.XMLObjectReader;
038:
039: import org.apache.commons.configuration.PropertiesConfiguration;
040: import org.apache.ddlutils.model.Database;
041: import org.apache.ddlutils.model.Table;
042: import org.apache.jetspeed.serializer.objects.JSGroup;
043: import org.apache.log4j.Level;
044: import org.apache.log4j.Logger;
045:
046: /**
047: * Jetspeed Serializer DDL- Application
048: *
049: * invoke with mandatory either
050: * <p>
051: * -I directory or filename of schema files for input, if directory all xml
052: * files will be processed
053: * </p>
054: * and/or
055: * <p>
056: * -O schema file for output log of current database
057: * </p>
058: * and (if -I denotes a directory:)
059: * <p>
060: * -x if directory list provided for input schemas this pattern is excluded
061: * </p>
062: * <p>
063: * -s name of the order file (if schema directory)
064: * </p>
065: *
066: * <p>
067: * -m if directory list provided this is the merge file to use. If not set here
068: * or in the properties file, a hardcoded version is used
069: * </p>
070: *
071: * <p>
072: * note that - if -I and -O are specified, the output file will contain the
073: * UPDATED database
074: * </p>
075: * invoke with (optional) parameters
076: * <p>
077: * -P propertyFileName, for settings
078: * <p>
079: * -R (flag) replace : overwrites default "UPDATE" and clears out the database
080: * before processing (ignored with -O option above)
081: * </p>
082: *
083: * <p>
084: * -dn databaseName, for example MYSQL or ORACLE10
085: * </p>
086: * <p>
087: * -dc driverClass, for example com.mysql.jdbc.Driver
088: * </p>
089: * <p>
090: * -ds url, ruls according to the driver used, URL needs to point to the correct
091: * database
092: * </p>
093: * <p>
094: * -du user, user with create/drop etc. rights on the database
095: * </p>
096: * <p>
097: * -dp password
098: * </p>
099: * <p>
100: * -l log4j-level, ERROR (default), WARN, INFO
101: * </p>
102: *
103: * @author <a href="mailto:hajo@bluesunrise.com">Hajo Birthelmer</a>
104: * @version $Id: $
105: */
106: public class JetspeedDDLApplication {
107: public static final String JNDI_DS_NAME = "jetspeed";
108:
109: String propertyFileName = null;
110: String exludeFileName = null;
111: String orderFileName = null;
112:
113: String logLevel = null;
114:
115: PropertiesConfiguration configuration = null;
116:
117: boolean doImport = false;
118: boolean doExport = false;
119: String schemaDirectory = null; // if specified all xml files in that
120: // directory will be processed
121: String outputFile = null; // if specified the database schema will be
122: // exported to that file
123: boolean overwrite = false; // default, do not overwrite the database
124: // (ignored if only output)
125: String driverClass = null; // jdbc driver
126: String url = null; // jdbc url to database
127: String user = null; // user
128: String password = null; // password
129: String databaseName = null;
130:
131: String[] filesToProcess = null;
132:
133: String mergeFile = null; //name of XSLT merge file
134: String[] args = null;
135:
136: public static void main(String[] args) throws Exception {
137: JetspeedDDLApplication app = new JetspeedDDLApplication();
138: app.processArguments(args);
139: }
140:
141: public JetspeedDDLApplication() {
142: }
143:
144: /**
145: * ensure that we have valid database settings
146: *
147: */
148: private void checkDBSettings() {
149: if (databaseName == null)
150: databaseName = System.getProperty(
151: "org.apache.jetspeed.database.databaseName",
152: "mysql");
153: if (driverClass == null)
154: driverClass = System.getProperty(
155: "org.apache.jetspeed.database.driverClass",
156: "com.mysql.jdbc.Driver");
157: if (url == null)
158: url = System.getProperty(
159: "org.apache.jetspeed.database.url",
160: "jdbc:mysql://localhost/j2test");
161: if (user == null)
162: user = System.getProperty(
163: "org.apache.jetspeed.database.user", "user");
164: if (password == null)
165: password = System
166: .getProperty(
167: "org.apache.jetspeed.database.password",
168: "password");
169:
170: if (driverClass == null)
171: throw new IllegalArgumentException(
172: "Can't proceed without a valid driver");
173: if (url == null)
174: throw new IllegalArgumentException(
175: "Can't proceed without a valid url to the target database");
176: if (user == null)
177: throw new IllegalArgumentException(
178: "Can't proceed without a valid database user");
179: return;
180: }
181:
182: /**
183: * parse arguments for process instructions, order and exclude files as well
184: * as optional database arguments
185: *
186: */
187: private void parseArguments() {
188: // Parse all the command-Oine arguments
189: for (int n = 0; n < args.length; n++) {
190: if (args[n].equals("-I")) {
191: doImport = true;
192: schemaDirectory = args[++n];
193: } else if (args[n].equals("-O")) {
194: doExport = true;
195: outputFile = args[++n];
196: } else if (args[n].equals("-s")) {
197: orderFileName = args[++n];
198: } else if (args[n].equals("-x")) {
199: exludeFileName = args[++n];
200: } else if (args[n].equals("-m")) {
201: mergeFile = args[++n];
202: } else if (args[n].equals("-R"))
203: overwrite = true;
204: else if (args[n].equals("-dn")) {
205: databaseName = args[++n];
206: } else if (args[n].equals("-dc"))
207: driverClass = args[++n];
208: else if (args[n].equals("-ds"))
209: url = args[++n];
210: else if (args[n].equals("-du")) {
211: if (((n + 1) >= args.length)
212: || args[n + 1].startsWith("-d")) {
213: user = "";
214: } else {
215: user = args[++n];
216: }
217: } else if (args[n].equals("-dp")) {
218: if (((n + 1) >= args.length)
219: || args[n + 1].startsWith("-d")) {
220: password = "";
221: } else {
222: password = args[++n];
223: }
224: } else if (args[n].equals("-P"))
225: propertyFileName = args[++n];
226: else if (args[n].equals("-l"))
227: logLevel = args[++n];
228:
229: else
230: throw new IllegalArgumentException("Unknown argument: "
231: + args[n]);
232: }
233:
234: }
235:
236: /**
237: * process provided filename or directory name
238: *
239: * @return one or more files to be processed
240: */
241: private String[] parseFiles() {
242: String[] fileList = null;
243: try {
244: File dir = new File(schemaDirectory);
245: if (!(dir.exists()))
246: return fileList;
247: if (!(dir.isDirectory())) {
248: fileList = new String[1];
249: fileList[0] = schemaDirectory;
250: return fileList;
251: }
252: // Handling a directory
253: LocalFilenameFilter filter = new LocalFilenameFilter(
254: exludeFileName, orderFileName);
255: File[] files = dir.listFiles(filter);
256: if (files == null)
257: return fileList;
258:
259: fileList = new String[files.length];
260: String sortorderFile = filter.getSortFile();
261: if (sortorderFile == null) {
262: for (int i = 0; i < files.length; i++)
263: fileList[i] = files[i].getAbsolutePath();
264: return fileList;
265: }
266: try {
267: ArrayList list = readOrderFile(sortorderFile);
268: fileList = new String[files.length];
269: if ((list == null) || (list.size() == 0)) {
270: for (int i = 0; i < files.length; i++)
271: fileList[i] = files[i].getAbsolutePath();
272: return fileList;
273: }
274: String[] tempList = new String[files.length];
275: for (int i = 0; i < files.length; i++)
276: tempList[i] = files[i].getName();
277: Iterator _it = list.iterator();
278: int j = 0;
279: while (_it.hasNext()) {
280: String filename = null;
281: try {
282: filename = ((JSGroup) _it.next()).getName();
283: } catch (Exception eeee) {
284: }
285: if (filename != null) {
286: for (int i = 0; i < files.length; i++) {
287: if (filename.equalsIgnoreCase(tempList[i])) {
288: fileList[j++] = files[i]
289: .getAbsolutePath();
290: tempList[i] = null;
291: }
292: }
293: }
294: }
295: for (int i = 0; i < files.length; i++) {
296: if (tempList[i] != null)
297: fileList[j++] = files[i].getAbsolutePath();
298: }
299: return fileList;
300: } catch (Exception eee) {
301: eee.printStackTrace();
302: return null;
303: }
304:
305: } catch (Exception e) {
306: e.printStackTrace();
307: throw new IllegalArgumentException(
308: "Processing the schema-directory "
309: + schemaDirectory + " caused exception "
310: + e.getLocalizedMessage());
311: }
312:
313: }
314:
315: /**
316: * setup environment by processing all arguments and call requested process
317: * routine
318: *
319: * @param arguments
320: * @throws Exception
321: */
322: private void processArguments(String[] arguments) throws Exception {
323: this .args = arguments;
324: if (args == null)
325: throw new IllegalArgumentException(
326: "Either a schema directory, a schema file or an output filename have to be defined (-D followed by a driectory, -I or -O followed by the filename");
327:
328: parseArguments();
329:
330: processPropertyFile();
331:
332: checkDBSettings();
333:
334: /**
335: * The only required argument is the filename for either export or
336: * import
337: */
338: if ((!doImport) && (!doExport))
339: throw new IllegalArgumentException(
340: "Either a schema directory, a schema file or an output filename have to be defined (-I or -O followed by the directory-/filename");
341:
342: if (doImport) {
343: filesToProcess = parseFiles();
344: if (filesToProcess == null)
345: return;
346: }
347:
348: /** create the instruction map */
349: JetspeedDDLUtil ddlUtil = null;
350:
351: HashMap context = new HashMap();
352: context.put(JetspeedDDLUtil.DATASOURCE_DATABASENAME,
353: databaseName);
354: context.put(JetspeedDDLUtil.DATASOURCE_DRIVER, driverClass);
355: context.put(JetspeedDDLUtil.DATASOURCE_URL, url);
356: context.put(JetspeedDDLUtil.DATASOURCE_USERNAME, user);
357: context.put(JetspeedDDLUtil.DATASOURCE_PASSWORD, password);
358:
359: Logger logger = Logger.getLogger("org.apache.ddlutils");
360: Level level = logger.getLevel();
361: if (logLevel == null)
362: logger.setLevel(Level.ERROR);
363: else if (logLevel.equalsIgnoreCase("INFO"))
364: logger.setLevel(Level.INFO);
365: else if (logLevel.equalsIgnoreCase("WARN"))
366: logger.setLevel(Level.WARN);
367: else
368: logger.setLevel(Level.ERROR);
369:
370: try {
371: ddlUtil = new JetspeedDDLUtil();
372: ddlUtil.startUp();
373: ddlUtil.init(context);
374: } catch (Exception e) {
375: System.err.println("Failed to initialize Utility!!!!!");
376: e.printStackTrace();
377: System.exit(-1);
378: }
379: try {
380: if (doImport)
381: processImport(ddlUtil);
382: if (doExport)
383: processExport(ddlUtil);
384: } catch (Exception e) {
385: System.err.println("Failed to process XML "
386: + (doExport ? "export" : "import") + ":" + e);
387: e.printStackTrace();
388: } finally {
389: try {
390: logger.setLevel(level);
391: if (ddlUtil != null)
392: ddlUtil.tearDown();
393: } catch (Exception e1) {
394: System.out
395: .println("starter framework teardown caused exception "
396: + e1.getLocalizedMessage());
397: e1.printStackTrace();
398:
399: }
400: }
401:
402: }
403:
404: /**
405: * create/alter database
406: *
407: * @param ddlUtil
408: */
409: private void processImport(JetspeedDDLUtil ddlUtil) {
410: String file = null;
411: if ((filesToProcess == null) || (filesToProcess.length == 0))
412: return;
413: if (filesToProcess.length > 1)
414: file = mergeFiles(filesToProcess);
415:
416: System.out.println("Importing " + file);
417: Database db = ddlUtil.createDatabaseSchemaFromXML(file);
418: try {
419: if (overwrite)
420: ddlUtil.createDatabase(db); // overwrite existing
421: // database
422: else
423: ddlUtil.alterDatabase(db);
424: System.out.println("Importing " + file + " completed");
425: } catch (Exception ePr) {
426: ePr.printStackTrace();
427: // continue with the process despite that one of the files was
428: // bad...
429: }
430:
431: }
432:
433: /**
434: * Helper routine to create a temporary file
435: *
436: * @param suffix
437: * @return
438: */
439: private File createTemp(String suffix) {
440: try {
441: // Create temp file.
442: File temp = File.createTempFile("tmp", suffix);
443:
444: // Delete temp file when program exits.
445: temp.deleteOnExit();
446: return temp;
447: } catch (IOException e) {
448: System.out.println("Failed to create temproary file with "
449: + e.getLocalizedMessage());
450: e.printStackTrace();
451: return null;
452: }
453:
454: }
455:
456: /**
457: * Open the merge file from disk
458: *
459: * @param fileName
460: * @return
461: */
462: private File createXSLTFromFile(String fileName) {
463: if (fileName == null)
464: return null;
465: try {
466: File f = new File(fileName);
467: if (f.exists())
468: return f;
469: return null;
470: } catch (Exception e) {
471: System.out.println("Failed to open merge template "
472: + e.getLocalizedMessage());
473: e.printStackTrace();
474: return null;
475: }
476: }
477:
478: /**
479: * If everything else fails, use a hardcoded XSLT here
480: *
481: * @return
482: */
483: private File createXSLTFromMemory() {
484: StringBuffer buffer = new StringBuffer();
485:
486: buffer.append("<?xml version=\"1.0\"?>");
487: buffer
488: .append("<xslt:transform version=\"1.0\" xmlns:xslt=\"http://www.w3.org/1999/XSL/Transform\">");
489: buffer
490: .append("<!-- Simple template to merge two database schemas into one -->");
491: buffer.append("<xslt:param name=\"fileTwo\" />");
492: buffer.append("<xslt:template match=\"/\">");
493:
494: buffer.append("<xslt:message>");
495: buffer
496: .append("<xslt:text /> Merging input with '<xslt:value-of select=\"$fileTwo\"/>");
497: buffer.append("<xslt:text>'</xslt:text>");
498: buffer.append("</xslt:message>");
499: buffer.append("<xslt:if test=\"string($fileTwo)=''\">");
500: buffer.append("<xslt:message terminate=\"yes\">");
501: buffer
502: .append("<xslt:text>No input file specified (parameter 'fileTwo')</xslt:text>");
503: buffer.append("</xslt:message>");
504: buffer.append("</xslt:if>");
505: buffer.append("<database name=\"generic\">");
506: buffer.append("<xslt:apply-templates />");
507: buffer.append("</database>");
508: buffer.append("</xslt:template>");
509: buffer.append("<xslt:template match=\"database\">");
510: buffer.append("<xslt:apply-templates />");
511: buffer
512: .append("<xslt:apply-templates select=\"document($fileTwo)/database/table\"/>");
513: buffer.append("</xslt:template>");
514:
515: buffer.append("<xslt:template match=\"@*|node()\">");
516: buffer.append("<xslt:copy>");
517: buffer.append("<xslt:apply-templates select=\"@*|node()\"/>");
518: buffer.append("</xslt:copy>");
519: buffer.append("</xslt:template>");
520: buffer.append("</xslt:transform>");
521:
522: File xslt = createTemp(".xslt");
523: try {
524: // Write to temp file
525:
526: BufferedWriter out = new BufferedWriter(
527: new FileWriter(xslt));
528: out.write(buffer.toString());
529: out.close();
530: return xslt;
531: } catch (Exception e) {
532: e.printStackTrace();
533: return null;
534: }
535: }
536:
537: /**
538: * process of merging two or more schema files into one schema file.
539: *
540: * @param fileList The filelist contains a (potentially) ordered list of schemas
541: * @return The name of the created temporary schema file
542: */
543: private String mergeFiles(String[] fileList) {
544: try {
545: File xsltFile = createXSLTFromFile(mergeFile);
546: if (xsltFile == null)
547: xsltFile = createXSLTFromMemory();
548: Source xslt = new StreamSource(xsltFile);
549: Transformer transformer = TransformerFactory.newInstance()
550: .newTransformer(xslt);
551:
552: String sourceName = fileList[0];
553: File target = null;
554: for (int i = 1; i < fileList.length; i++) {
555: File soureFile = new File(sourceName);
556: Source source = new StreamSource(soureFile);
557: // JAXP reads data using the Source interface
558: target = createTemp(".xml");
559:
560: Result targetResult = new StreamResult(target);
561: File f = new File(fileList[i]);
562: String other = "file:///" + f.getCanonicalPath(); // required on Win-platforms
563: other = other.replace('\\', '/');
564:
565: transformer.setParameter("fileTwo", other);
566: transformer.transform(source, targetResult);
567: sourceName = target.getAbsolutePath();
568: }
569: return sourceName;
570:
571: } catch (Exception e) {
572: e.printStackTrace();
573: return null;
574: }
575:
576: }
577:
578: /**
579: * read database schema to file
580: *
581: */
582: private void processExport(JetspeedDDLUtil ddlUtil) {
583: // TODO: implement
584: ddlUtil.writeDatabaseSchematoFile(this .outputFile);
585:
586: }
587:
588: /**
589: * read the property file and read what has not yet been defined
590: */
591: private void processPropertyFile() {
592: /** get system property definition */
593: if (propertyFileName == null)
594: propertyFileName = System.getProperty(
595: "org.apache.jetspeed.xml.ddlUtil.configuration",
596: null);
597:
598: if (propertyFileName == null)
599: return;
600: try {
601: configuration = new PropertiesConfiguration(
602: propertyFileName);
603: } catch (Exception e) {
604: e.printStackTrace();
605: return;
606: }
607: if (configuration != null) {
608: /** only read what was not defined on the command line */
609:
610: if (driverClass == null)
611: driverClass = configuration.getString("driverClass");
612: if (url == null)
613: url = configuration.getString("url");
614: if (user == null)
615: user = configuration.getString("user");
616: if (password == null)
617: password = configuration.getString("password");
618: if (mergeFile == null)
619: mergeFile = configuration.getString("mergeFile");
620: if (!(doImport)) {
621: schemaDirectory = configuration.getString("schema");
622: if (schemaDirectory != null)
623: doImport = true;
624: }
625: if (!(doExport)) {
626: outputFile = configuration.getString("outputFile");
627: if (outputFile != null)
628: doExport = true;
629: }
630: if (logLevel == null)
631: logLevel = configuration.getString("loglevel");
632:
633: }
634:
635: }
636:
637: /*
638: private static String[] getTokens(String _line)
639: {
640: if ((_line == null) || (_line.length() == 0))
641: return null;
642:
643: StringTokenizer st = new StringTokenizer(_line, ",");
644: ArrayList list = new ArrayList();
645:
646: while (st.hasMoreTokens())
647: list.add(st.nextToken());
648: String[] s = new String[list.size()];
649: for (int i = 0; i < list.size(); i++)
650: s[i] = (String) list.get(i);
651: return s;
652: }
653: */
654:
655: public List getRows(JetspeedDDLUtil ddlUtil, String tableName) {
656: Table table = ddlUtil.getModel().findTable(tableName,
657: ddlUtil.getPlatform().isDelimitedIdentifierModeOn());
658:
659: return ddlUtil.getPlatform().fetch(ddlUtil.getModel(),
660: getSelectQueryForAllString(ddlUtil, table),
661: new Table[] { table });
662: }
663:
664: public String getSelectQueryForAllString(JetspeedDDLUtil ddlUtil,
665: Table table) {
666:
667: StringBuffer query = new StringBuffer();
668:
669: query.append("SELECT * FROM ");
670: if (ddlUtil.getPlatform().isDelimitedIdentifierModeOn()) {
671: query.append(ddlUtil.getPlatform().getPlatformInfo()
672: .getDelimiterToken());
673: }
674: query.append(table.getName());
675: if (ddlUtil.getPlatform().isDelimitedIdentifierModeOn()) {
676: query.append(ddlUtil.getPlatform().getPlatformInfo()
677: .getDelimiterToken());
678: }
679: System.out.println(query.toString());
680: return query.toString();
681: }
682:
683: /**
684: * read an xml file describing the basic order of the files to be processed
685: *
686: * @param importFileName
687: * @return
688: * @throws SerializerException
689: */
690:
691: private ArrayList readOrderFile(String importFileName) {
692: XMLObjectReader reader = null;
693:
694: XMLBinding binding = new XMLBinding();
695: binding.setAlias(ArrayList.class, "ProcessOrder");
696: binding.setAlias(JSGroup.class, "File");
697:
698: ArrayList snap = null;
699: try {
700: reader = XMLObjectReader.newInstance(new FileInputStream(
701: importFileName));
702: } catch (Exception e) {
703: e.printStackTrace();
704: return null;
705: }
706: try {
707: reader.setBinding(binding);
708: snap = (ArrayList) reader.read("ProcessOrder",
709: ArrayList.class);
710:
711: } catch (Exception e) {
712: e.printStackTrace();
713: } finally {
714: /** ensure the reader is closed */
715: try {
716: reader.close();
717: } catch (Exception e1) {
718: /**
719: * don't do anything with this exception - never let the bubble
720: * out of the finally block
721: */
722: }
723: }
724: return snap;
725: }
726:
727: class LocalFilenameFilter implements FilenameFilter {
728:
729: String exclude = null;
730: String sortFile = null;
731: String sort = null;
732:
733: String getSortFile() {
734: return sortFile;
735: }
736:
737: LocalFilenameFilter(String exclude, String sort) {
738: this .exclude = exclude;
739: this .sort = sort;
740:
741: }
742:
743: public boolean accept(File dir, String name) {
744: if (exclude != null)
745: if (name.equalsIgnoreCase(exclude))
746: return false;
747: if (sort != null)
748: if (name.equalsIgnoreCase(sort)) {
749: sortFile = dir.getAbsolutePath() + "/" + sort;
750: return false;
751: }
752:
753: return name.endsWith(".xml");
754: }
755:
756: }
757:
758: }
|