001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/chat/tags/sakai_2-4-1/chat-impl/impl/src/java/org/sakaiproject/chat2/model/impl/ChatDataMigration.java $
003: * $Id: ChatDataMigration.java 30493 2007-05-14 17:51:27Z ajpoland@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2007 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/
021:
022: /**
023: *
024: */package org.sakaiproject.chat2.model.impl;
025:
026: import java.io.BufferedWriter;
027: import java.io.FileWriter;
028: import java.io.PrintWriter;
029: import java.sql.Clob;
030: import java.sql.Connection;
031: import java.sql.ResultSet;
032: import java.sql.Statement;
033: import java.text.MessageFormat;
034: import java.util.Date;
035:
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038: import org.sakaiproject.db.api.SqlService;
039: import org.sakaiproject.util.ResourceLoader;
040: import org.sakaiproject.util.Xml;
041: import org.w3c.dom.Document;
042: import org.w3c.dom.Element;
043:
044: /**
045: * @author chrismaurer
046: *
047: */
048: public class ChatDataMigration {
049: protected final transient Log logger = LogFactory
050: .getLog(getClass());
051:
052: private boolean debug = false;
053: private String outputFile = "/chat-migration.sql";
054:
055: private SqlService sqlService = null;
056:
057: private Statement stmt;
058:
059: private ResourceLoader toolBundle;
060:
061: private boolean performChatMigration = false;
062: private boolean chatMigrationExecuteImmediate = true;
063: private boolean escapeSpecialCharactersInArchive = true;
064: private boolean escapeSpecialCharactersInLive = false;
065:
066: /** init thread - so we don't wait in the actual init() call */
067: public class ChatDataMigrationThread extends Thread {
068: /**
069: * construct and start the init activity
070: */
071: public ChatDataMigrationThread() {
072: //m_ready = false;
073: start();
074: }
075:
076: /**
077: * run the init
078: */
079: public void run() {
080: try {
081: load();
082: } catch (Exception e) {
083: logger.warn("Error with ChatDataMigrationThread.run()",
084: e);
085: }
086: }
087: }
088:
089: /**
090: * Called on after the startup of the singleton. This sets the global
091: * list of functions which will have permission managed by sakai
092: * @throws Exception
093: */
094: protected void init() throws Exception {
095: logger.info("init()");
096:
097: try {
098: if (performChatMigration) {
099: new ChatDataMigrationThread();
100: //load();
101: }
102: } catch (Exception e) {
103: logger.warn("Error with ChatDataMigration.init()", e);
104: }
105:
106: }
107:
108: /**
109: * Destroy
110: */
111: public void destroy() {
112: logger.info("destroy()");
113: }
114:
115: public void load() throws Exception {
116: printDebug("*******outputDir: " + outputFile);
117: logger.info("Running Chat Migration; output file: "
118: + outputFile + " immediate: "
119: + chatMigrationExecuteImmediate);
120: Connection connection = null;
121:
122: PrintWriter sqlFile = new PrintWriter(new BufferedWriter(
123: new FileWriter(outputFile, false)), true);
124:
125: try {
126:
127: sqlFile.println("--" + getDbPrefix());
128: /*
129: //Add a new columns for us to keep track
130: String alterChannelTable = getMessageFromBundle("alter.channel");
131: String alterMessageTable = getMessageFromBundle("alter.message");
132:
133: sqlFile.println(alterChannelTable);
134: sqlFile.println(alterMessageTable);
135:
136: if (chatMigrationExecuteImmediate) {
137: try {
138: sqlService.dbWrite(null, alterChannelTable, null);
139: }
140: catch (Exception e) {
141: logger.warn("Channel table has already been modified to accept migration data.");
142: }
143: try {
144: sqlService.dbWrite(null, alterMessageTable, null);
145: }
146: catch (Exception e) {
147: logger.warn("Message table has already been modified to accept migration data.");
148: }
149: }
150: */
151:
152: sqlFile.println();
153:
154: connection = sqlService.borrowConnection();
155: printDebug("*******BORROWED A CONNECTION");
156: runChannelMigration(connection, sqlFile);
157:
158: runMessageMigration(connection, sqlFile);
159:
160: } catch (Exception e) {
161: printDebug(e.toString());
162: logger.error(e.getMessage());
163: throw new Exception(e);
164: } finally {
165: if (connection != null) {
166: try {
167: sqlService.returnConnection(connection);
168: } catch (Exception e) {
169: // can't do anything with this.
170: }
171: }
172: }
173: logger.info("Chat migration complete");
174: }
175:
176: protected void runChannelMigration(Connection con,
177: PrintWriter output) {
178: logger.debug("runChannelMigration()");
179: printDebug("*******GETTING CHANNELS");
180:
181: String sql = getMessageFromBundle("select.oldchannels");
182:
183: try {
184: stmt = con.createStatement();
185: ResultSet rs = stmt.executeQuery(sql);
186:
187: try {
188: while (rs.next()) {
189: /*
190: * CHANNEL_ID
191: * XML
192: */
193: String oldId = rs.getString("CHANNEL_ID");
194: Object xml = rs.getObject("XML");
195:
196: printDebug("*******FOUND CHANNEL: " + oldId);
197: printDebug("*******FOUND CHANNEL: " + xml);
198:
199: Document doc = null;
200: try {
201: doc = Xml.readDocumentFromString((String) xml);
202: } catch (ClassCastException cce) {
203: Clob xmlClob = (Clob) xml;
204: doc = Xml.readDocumentFromStream(xmlClob
205: .getAsciiStream());
206: }
207:
208: // verify the root element
209: Element root = doc.getDocumentElement();
210: String context = root.getAttribute("context");
211: String title = root.getAttribute("id");
212: String newChannelId = escapeSpecialCharsForId(oldId);
213:
214: //TODO Chat lookup the config params?
215: String outputSql = getMessageFromBundle(
216: "insert.channel", new Object[] {
217: newChannelId, context, null,
218: escapeSingleQuotes(title), "",
219: "SelectMessagesByTime", 3, 0,
220: escapeSingleQuotes(oldId), 1 });
221: /*
222: * CHANNEL_ID,
223: * CONTEXT,
224: * CREATION_DATE,
225: * title,
226: * description,
227: * filterType,
228: * filterParam,
229: * contextDefaultChannel,
230: * migratedChannelId,
231: * ENABLE_USER_OVERRIDE
232: */
233:
234: String escapedSql = escapeConfiguredCharactersOptional(outputSql);
235: String sqlToRun = (isEscapeSpecialCharactersInLive()) ? escapedSql
236: : outputSql;
237: String sqlToWrite = (isEscapeSpecialCharactersInArchive()) ? escapedSql
238: : outputSql;
239:
240: output.println(sqlToWrite + ";");
241: if (chatMigrationExecuteImmediate) {
242: sqlService.dbWrite(null, sqlToRun, null);
243: }
244:
245: //Get the messages for each channel
246: //runMessageMigration(con, output, oldId, newChannelId);
247:
248: }
249: } finally {
250: rs.close();
251: }
252: } catch (Exception e) {
253: logger.error("error selecting data with this sql: " + sql);
254: logger.error("", e);
255: } finally {
256: try {
257: stmt.close();
258: } catch (Exception e) {
259: }
260: }
261: logger.debug("Migration task fininshed: runChannelMigration()");
262: }
263:
264: protected void runMessageMigration(Connection con,
265: PrintWriter output) {
266: logger.debug("runMessageMigration()");
267: printDebug("*******GETTING MESSAGES");
268:
269: //String sql = getMessageFromBundle("select.oldmessages", new Object[]{oldChannelId});
270: String sql = getMessageFromBundle("select.oldmessages");
271: //String sql = "select c.channel_id, c.xml from chat_channel c";
272:
273: try {
274: stmt = con.createStatement();
275: ResultSet rs = stmt.executeQuery(sql);
276:
277: try {
278: while (rs.next()) {
279: /*
280: * MESSAGE_ID
281: * CHANNEL_ID
282: * XML
283: * OWNER
284: * MESSAGE_DATE
285: */
286: String oldMessageId = rs.getString("MESSAGE_ID");
287: String oldChannelId = rs.getString("CHANNEL_ID");
288: Object xml = rs.getObject("XML");
289: String owner = rs.getString("OWNER");
290: Date messageDate = rs.getTimestamp("MESSAGE_DATE");
291:
292: printDebug("*******FOUND MESSAGE: " + oldMessageId);
293: printDebug("*******FOUND MESSAGE: " + xml);
294:
295: Document doc = null;
296: try {
297: doc = Xml.readDocumentFromString((String) xml);
298: } catch (ClassCastException cce) {
299: Clob xmlClob = (Clob) xml;
300: doc = Xml.readDocumentFromStream(xmlClob
301: .getAsciiStream());
302: }
303:
304: // verify the root element
305: Element root = doc.getDocumentElement();
306: String body = Xml.decodeAttribute(root, "body");
307: //String body = root.getAttribute("body");
308: //String body = "test";
309:
310: String newMessageId = oldMessageId;
311: //newMessageId = escapeSpecialCharsForId(newMessageId);
312:
313: String outputSql = getMessageFromBundle(
314: "insert.message",
315: new Object[] {
316: escapeSpecialCharsForId(newMessageId),
317: escapeSpecialCharsForId(oldChannelId),
318: owner, messageDate,
319: escapeSingleQuotes(body),
320: oldMessageId });
321: /*
322: * insert into CHAT2_MESSAGE (MESSAGE_ID, CHANNEL_ID, OWNER, MESSAGE_DATE, BODY) \
323: values ('{0}', '{1}', '{2}', '{3}', '{4}');
324:
325: */
326:
327: String escapedSql = escapeConfiguredCharactersOptional(outputSql);
328: String sqlToRun = (isEscapeSpecialCharactersInLive()) ? escapedSql
329: : outputSql;
330: String sqlToWrite = (isEscapeSpecialCharactersInArchive()) ? escapedSql
331: : outputSql;
332:
333: output.println(sqlToWrite + ";");
334: if (chatMigrationExecuteImmediate) {
335: sqlService.dbWrite(null, sqlToRun, null);
336: }
337: }
338: } finally {
339: rs.close();
340: }
341: } catch (Exception e) {
342: logger.error("error selecting data with this sql: " + sql);
343: logger.error("", e);
344: } finally {
345: try {
346: stmt.close();
347: } catch (Exception e) {
348: }
349: }
350: logger.debug("Migration task fininshed: runMessageMigration()");
351:
352: }
353:
354: /**
355: * Escapes special characters that may be bad in sql statements
356: * -- "/" is replaced with "_"
357: * @param input Original string to parse
358: * @return A string with any special characters escaped
359: */
360: protected String escapeSpecialCharsForId(String input) {
361: String output = input.replaceAll("/", "_");
362: output = escapeSingleQuotes(output);
363: return output;
364: }
365:
366: /**
367: * Escapes single quotes
368: * -- "'" is replaced with "''"
369: * @param input Original string to parse
370: * @return A string with any special characters escaped
371: */
372: protected String escapeSingleQuotes(String input) {
373: String output = input.replaceAll("'", "''");
374: return output;
375: }
376:
377: /**
378: * Does all of the escaping for the message body
379: * @param input
380: * @return
381: */
382: protected String escapeConfiguredCharactersOptional(String input) {
383: //String output = escapeSingleQuotes(input);
384: String output = input;
385: String escapeChar = getMessageFromBundle("escapeChar");
386: String[] escapedChars = getMessagesFromBundle("escapedChars");
387:
388: for (String theChar : escapedChars) {
389: output = output.replaceAll(theChar, escapeChar
390: .concat(theChar));
391: }
392: return output;
393: }
394:
395: protected void printDebug(String message) {
396: if (debug) {
397: //System.out.println(message);
398: //logger.debug(message);
399: logger.info("DEBUG: " + message);
400: }
401: }
402:
403: /**
404: * Looks up the db vendor from the SqlService
405: * @return The string for the db vendor (mysql, oracle, hsqldb, etc)
406: */
407: protected String getDbPrefix() {
408: return sqlService.getVendor();
409: }
410:
411: /**
412: * Looks up the sql statements defined in chat-sql.properties. Appends the db
413: * vendor key to the beginning of the message (oracle.select.channel, mysql.select.channel, etc)
414: * @param key
415: * @return
416: */
417: private String getMessageFromBundle(String key) {
418: if (toolBundle == null)
419: toolBundle = new ResourceLoader("chat-sql");
420:
421: return toolBundle.getString(getDbPrefix().concat("." + key));
422: }
423:
424: /**
425: * Looks up the sql statements defined in chat-sql.properties. Appends the db
426: * vendor key to the beginning of the message (oracle.select.channel, mysql.select.channel, etc)
427: * @param key
428: * @return
429: */
430: private String[] getMessagesFromBundle(String key) {
431: if (toolBundle == null)
432: toolBundle = new ResourceLoader("chat-sql");
433:
434: return toolBundle.getStrings(getDbPrefix().concat("." + key));
435: }
436:
437: private String getMessageFromBundle(String key, Object[] args) {
438: return MessageFormat.format(getMessageFromBundle(key), args);
439: }
440:
441: public boolean isChatMigrationExecuteImmediate() {
442: return chatMigrationExecuteImmediate;
443: }
444:
445: public void setChatMigrationExecuteImmediate(
446: boolean chatMigrationExecuteImmediate) {
447: this .chatMigrationExecuteImmediate = chatMigrationExecuteImmediate;
448: }
449:
450: public boolean isPerformChatMigration() {
451: return performChatMigration;
452: }
453:
454: public void setPerformChatMigration(boolean performChatMigration) {
455: this .performChatMigration = performChatMigration;
456: }
457:
458: public boolean isDebug() {
459: return debug;
460: }
461:
462: public void setDebug(boolean debug) {
463: this .debug = debug;
464: }
465:
466: public String getOutputFile() {
467: return outputFile;
468: }
469:
470: public void setOutputFile(String outputFile) {
471: this .outputFile = outputFile;
472: }
473:
474: public SqlService getSqlService() {
475: return sqlService;
476: }
477:
478: public void setSqlService(SqlService sqlService) {
479: this .sqlService = sqlService;
480: }
481:
482: public boolean isEscapeSpecialCharactersInArchive() {
483: return escapeSpecialCharactersInArchive;
484: }
485:
486: public void setEscapeSpecialCharactersInArchive(
487: boolean escapeSpecialCharactersInArchive) {
488: this .escapeSpecialCharactersInArchive = escapeSpecialCharactersInArchive;
489: }
490:
491: public boolean isEscapeSpecialCharactersInLive() {
492: return escapeSpecialCharactersInLive;
493: }
494:
495: public void setEscapeSpecialCharactersInLive(
496: boolean escapeSpecialCharactersInLive) {
497: this.escapeSpecialCharactersInLive = escapeSpecialCharactersInLive;
498: }
499:
500: }
|