View Javadoc

1   /**
2    * Copyright 2004-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.torque.mojo;
17  
18  import java.io.ByteArrayInputStream;
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.List;
25  
26  import org.apache.commons.io.FileUtils;
27  import org.apache.commons.io.IOUtils;
28  import org.apache.commons.lang.StringUtils;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.MojoFailureException;
31  import org.apache.torque.util.CloverETLColumn;
32  import org.apache.torque.util.CloverETLTable;
33  
34  /**
35   * This project converts Clover ETL data generated by Kuali's Ant based tooling into a format that is consumable by the Maven Impex plugin
36   *
37   * @goal convertcloveretl
38   */
39  public class ConvertCloverETLMojo extends BaseMojo {
40  
41  	/**
42  	 * The source directory where Clover ETL data was generated
43  	 *
44  	 * @parameter expression="${impex.sourceDirectory}" default-value="${project.basedir}/src/main/resources"
45  	 * @required
46  	 */
47  	File sourceDir;
48  
49  	/**
50  	 * The output directory where maven-impex-plugin data will get generated
51  	 *
52  	 * @parameter expression="${impex.outputDir}" default-value="${project.build.directory}/impex"
53  	 * @required
54  	 */
55  	File outputDir;
56  
57  	/**
58  	 * The delimiter used by the Clover ETL data
59  	 *
60  	 * @parameter expression="${impex.delimiter}" default-value="|"
61  	 * @required
62  	 */
63  	String delimiter;
64  
65  	/**
66  	 * The schema.xml file for Clover ETL
67  	 *
68  	 * @parameter expression="${impex.schemaFilename}" default-value="schema.xml"
69  	 * @required
70  	 */
71  	String schemaFilename;
72  
73  	/**
74  	 * The name of the file containing the DTD for database schemas
75  	 *
76  	 * @parameter expression="${impex.databaseDTDFilename}" default-value="database.dtd"
77  	 * @required
78  	 */
79  	String databaseDTDFilename;
80  
81  	/**
82  	 * The name of the file containing the DTD for this database schema
83  	 *
84  	 * @parameter expression="${impex.dataDTDFilename}" default-value="data.dtd"
85  	 * @required
86  	 */
87  	String dataDTDFilename;
88  
89  	@Override
90  	protected void executeMojo() throws MojoExecutionException, MojoFailureException {
91  		getLog().info("Examining " + sourceDir.getAbsolutePath());
92  		handleSchema();
93  		handleData();
94  	}
95  
96  	protected void handleSchema() {
97  		try {
98  			handleDataDTD();
99  			File newSchemaFile = new File(outputDir + "/" + schemaFilename);
100 			File oldSchemaFile = new File(sourceDir + "/" + schemaFilename);
101 			getLog().info("Creating " + newSchemaFile.getCanonicalPath());
102 			FileUtils.copyFile(oldSchemaFile, newSchemaFile);
103 			File newDatabaseDTDFile = new File(outputDir + "/" + databaseDTDFilename);
104 			File oldDatabaseDTDFile = new File(sourceDir + "/" + databaseDTDFilename);
105 			getLog().info("Creating " + newDatabaseDTDFile.getCanonicalPath());
106 			FileUtils.copyFile(oldDatabaseDTDFile, newDatabaseDTDFile);
107 		} catch (IOException e) {
108 			throw new IllegalStateException("Unexpected IO error", e);
109 		}
110 	}
111 
112 	protected String[] parseAll(String s, String open, String close) {
113 		String[] tokens = StringUtils.substringsBetween(s, open, close);
114 		if (tokens == null) {
115 			return null;
116 		}
117 		for (int i = 0; i < tokens.length; i++) {
118 			tokens[i] = open + tokens[i] + close;
119 		}
120 		return tokens;
121 
122 	}
123 
124 	protected void handleDataDTD() throws IOException {
125 		File schemaFile = new File(sourceDir + "/" + schemaFilename);
126 		String contents = FileUtils.readFileToString(schemaFile);
127 		String[] tables = getTables(contents);
128 		getLog().info("Located " + tables.length + " schema tables");
129 		List<CloverETLTable> realTables = new ArrayList<CloverETLTable>();
130 		for (String table : tables) {
131 			CloverETLTable realTable = getDataDTDTable(table);
132 			realTables.add(realTable);
133 		}
134 		String content = getDataDTDContent(realTables);
135 		File dataDTDFile = new File(outputDir + "/" + dataDTDFilename);
136 		getLog().info("Creating " + dataDTDFile.getCanonicalPath());
137 		FileUtils.writeStringToFile(dataDTDFile, content);
138 	}
139 
140 	protected String getDataDTDContent(List<CloverETLTable> tables) {
141 		StringBuilder sb = new StringBuilder();
142 		sb.append(getProlog(tables));
143 		for (CloverETLTable table : tables) {
144 			sb.append(getTableDTDContent(table));
145 		}
146 		return sb.toString();
147 	}
148 
149 	protected String getTableDTDContent(CloverETLTable table) {
150 		StringBuilder sb = new StringBuilder();
151 		sb.append("<!ELEMENT " + table.getName() + " EMPTY>\n");
152 		sb.append("<!ATTLIST " + table.getName() + "\n");
153 		sb.append(getColumnDTDContent(table.getEtlColumns()));
154 		sb.append(">\n");
155 		return sb.toString();
156 	}
157 
158 	protected String getColumnDTDContent(List<CloverETLColumn> columns) {
159 		StringBuilder sb = new StringBuilder();
160 		for (CloverETLColumn column : columns) {
161 			sb.append("    ");
162 			sb.append(column.getName());
163 			sb.append(" ");
164 			sb.append("CDATA");
165 			sb.append(" ");
166 			sb.append(column.isRequired() ? "#IMPLIED" : "#REQUIRED");
167 			sb.append("\n");
168 		}
169 		return sb.toString();
170 	}
171 
172 	protected String getProlog(List<CloverETLTable> tables) {
173 		StringBuilder sb = new StringBuilder();
174 		sb.append("<!ELEMENT dataset (\n");
175 		for (int i = 0; i < tables.size(); i++) {
176 			CloverETLTable table = tables.get(i);
177 			sb.append("    " + table.getName());
178 			if (i < tables.size() - 1) {
179 				sb.append("|\n");
180 			} else {
181 				sb.append(")*>\n");
182 			}
183 		}
184 		return sb.toString();
185 	}
186 
187 	protected CloverETLTable getDataDTDTable(String s) {
188 		String tablename = StringUtils.substringBetween(s, "<table name=\"", "\"");
189 		String[] columns = parseAll(s, "<column ", "/>");
190 		List<CloverETLColumn> realColumns = new ArrayList<CloverETLColumn>();
191 		for (String column : columns) {
192 			realColumns.add(getCloverETLColumn(column));
193 		}
194 
195 		CloverETLTable table = new CloverETLTable();
196 		table.setName(tablename);
197 		table.setEtlColumns(realColumns);
198 		return table;
199 	}
200 
201 	protected CloverETLColumn getCloverETLColumn(String s) {
202 		String columnName = StringUtils.substringBetween(s, "name=\"", "\"");
203 		boolean required = s.contains("required=\"true\"");
204 		CloverETLColumn cec = new CloverETLColumn();
205 		cec.setName(columnName);
206 		cec.setRequired(required);
207 		return cec;
208 	}
209 
210 	protected String[] getTables(String contents) {
211 		String begin = "<table name=\"";
212 		String close = "</table>";
213 		return parseAll(contents, begin, close);
214 	}
215 
216 	protected String parse(String s, String begin, String close) {
217 		String between = StringUtils.substringBetween(s, begin, close);
218 		return begin + between + close;
219 	}
220 
221 	protected void handleData() {
222 		File dataDir = new File(sourceDir + "/data");
223 		File[] files = dataDir.listFiles();
224 		if (files == null) {
225 			getLog().info("Converting 0 Clover ETL data files");
226 		} else {
227 			getLog().info("Converting " + files.length + " Clover ETL data files");
228 			Arrays.sort(files);
229 			for (File file : files) {
230 				convertFile(file);
231 			}
232 		}
233 	}
234 
235 	protected void convertFile(File file) {
236 		try {
237 			CloverETLTable table = getTable(file);
238 			String xml = getXml(table);
239 			File outputFile = new File(outputDir + "/" + table.getName() + ".xml");
240 			getLog().info("Creating " + outputFile.getCanonicalPath());
241 			FileUtils.writeStringToFile(outputFile, xml);
242 		} catch (IOException e) {
243 			throw new IllegalStateException("Unexpected IO error", e);
244 		}
245 	}
246 
247 	protected CloverETLTable getTable(File file) throws IOException {
248 		String filename = file.getName().toUpperCase();
249 		int pos = filename.indexOf(".");
250 		String tablename = filename.substring(0, pos);
251 
252 		String content = FileUtils.readFileToString(file);
253 		String headerLine = content.substring(0, content.indexOf("\n"));
254 		String[] columns = StringUtils.splitByWholeSeparatorPreserveAllTokens(headerLine, delimiter);
255 
256 		for (int i = 0; i < columns.length; i++) {
257 			columns[i] = columns[i].toUpperCase();
258 		}
259 
260 		List<String[]> rows = getRows(content, columns, file);
261 
262 		CloverETLTable table = new CloverETLTable();
263 		table.setName(tablename);
264 		table.setColumns(Arrays.asList(columns));
265 		table.setRows(rows);
266 		return table;
267 	}
268 
269 	@SuppressWarnings("unchecked")
270 	protected List<String> readLines(String s) {
271 		try {
272 			InputStream in = new ByteArrayInputStream(s.getBytes());
273 			return IOUtils.readLines(in);
274 		} catch (IOException e) {
275 			throw new IllegalStateException("Unexpected IO error", e);
276 		}
277 
278 	}
279 
280 	protected List<String[]> getRows(String content, String[] columns, File file) {
281 		List<String> lines = readLines(content);
282 		List<String[]> rows = new ArrayList<String[]>();
283 		for (int i = 1; i < lines.size(); i++) {
284 			String line = lines.get(i);
285 			while (!line.endsWith(delimiter)) {
286 				i = i + 1;
287 				line = line + "\n" + lines.get(i);
288 			}
289 			String[] row = StringUtils.splitByWholeSeparatorPreserveAllTokens(line, delimiter);
290 			if (row.length != columns.length) {
291 				throw new IllegalStateException("Column count doesn't match. [" + file.getAbsolutePath() + ",row " + i + "] columns=" + columns.length + " row=" + row.length);
292 			}
293 			for (int j = 0; j < row.length; j++) {
294 				row[j] = escape(row[j]);
295 			}
296 			rows.add(row);
297 		}
298 		return rows;
299 	}
300 
301 	protected String escape(String s) {
302 		return s.replace("&", "&amp;").replace("<", "&lt;").replace("\"", "&quot;").replace("\n", "&#xa;").replace("\r", "&#xd;");
303 	}
304 
305 	protected String getXml(CloverETLTable table) {
306 		StringBuilder sb = new StringBuilder();
307 		sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
308 		sb.append("<!--  Auto-generated by the Maven Impex Plugin -->\n");
309 		sb.append("<!DOCTYPE dataset SYSTEM \"data.dtd\">\n");
310 		sb.append("<dataset>\n");
311 		List<String[]> rows = table.getRows();
312 		List<String> columns = table.getColumns();
313 		for (int i = 0; i < rows.size(); i++) {
314 			sb.append("    <" + table.getName());
315 			String[] row = rows.get(i);
316 			for (int j = 0; j < columns.size(); j++) {
317 				String column = columns.get(j);
318 				String value = row[j];
319 				if (!StringUtils.isBlank(value)) {
320 					sb.append(" " + column + "=" + '"' + value + '"');
321 				}
322 			}
323 			sb.append(" />\n");
324 		}
325 		sb.append("</dataset>\n");
326 		return sb.toString();
327 	}
328 
329 	public File getSourceDir() {
330 		return sourceDir;
331 	}
332 
333 	public void setSourceDir(File sourceDir) {
334 		this.sourceDir = sourceDir;
335 	}
336 
337 	public File getOutputDir() {
338 		return outputDir;
339 	}
340 
341 	public void setOutputDir(File outputDir) {
342 		this.outputDir = outputDir;
343 	}
344 
345 	public String getDelimiter() {
346 		return delimiter;
347 	}
348 
349 	public void setDelimiter(String delimiter) {
350 		this.delimiter = delimiter;
351 	}
352 
353 	public String getSchemaFilename() {
354 		return schemaFilename;
355 	}
356 
357 	public void setSchemaFilename(String schemaFilename) {
358 		this.schemaFilename = schemaFilename;
359 	}
360 
361 	public String getDatabaseDTDFilename() {
362 		return databaseDTDFilename;
363 	}
364 
365 	public void setDatabaseDTDFilename(String databaseDTDFilename) {
366 		this.databaseDTDFilename = databaseDTDFilename;
367 	}
368 
369 	public String getDataDTDFilename() {
370 		return dataDTDFilename;
371 	}
372 
373 	public void setDataDTDFilename(String dataDTDFilename) {
374 		this.dataDTDFilename = dataDTDFilename;
375 	}
376 }