1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.addam.ddlrun;
20
21
22 import javax.naming.InitialContext;
23 import javax.servlet.ServletContext;
24 import javax.servlet.ServletContextEvent;
25 import javax.servlet.ServletContextListener;
26 import javax.sql.DataSource;
27
28 import net.sourceforge.addam.ddlrun.custom.IDSCompletedScriptRetriever;
29 import net.sourceforge.addam.ddlrun.custom.IDSRunLogger;
30 import net.sourceforge.addam.ddlrun.custom.IDSStartFolderRetriever;
31 import net.sourceforge.addam.ddlrun.grammars.ScriptGrammar;
32 import net.sourceforge.addam.ddlrun.grammars.ScriptGrammarManager;
33 import net.sourceforge.addam.ddlrun.runners.DeploymentRunner;
34 import net.sourceforge.addam.ddlrun.runners.DeploymentRunnerFactory;
35 import net.sourceforge.addam.ddlrun.utils.ClassLoaderReaderFactory;
36 import net.sourceforge.addam.ddlrun.utils.ResourceReaderFactory;
37 import net.sourceforge.addam.ddlrun.utils.RunLogger;
38
39 import java.io.File;
40 import java.io.FileWriter;
41 import java.io.IOException;
42 import java.sql.Connection;
43 import java.sql.SQLException;
44 import java.text.MessageFormat;
45 import java.util.ArrayList;
46 import java.util.Date;
47 import java.util.Properties;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
50
51 /**
52 * @author TIM3
53 * @since Mar 25, 2005
54 */
55 public class ServletContainerUpgrader implements ServletContextListener, RunLogger {
56
57 ServletContext sCtx = null;
58 FileWriter errorLog = null;
59 private int scriptsExecuted = 0;
60 protected String dbversion = null;
61
62 /**
63 * kicks off an UpgradeRunner - requires a configured DataSource and a
64 * configured upgrade folder and upgrade script
65 */
66 public void contextInitialized(final ServletContextEvent event) {
67 sCtx = event.getServletContext();
68 errorLog = getErrorLogFileWriter(sCtx);
69 Connection connection = null;
70 try {
71 InitialContext iCtx = new InitialContext();
72 DataSource dataSource = null;
73 try {
74 dataSource = (DataSource) iCtx.lookup("java:comp/env/jdbc/AutoUpgradeDS");
75 } catch (Exception e) {
76
77 String logMessage = "unable to locate DataSource, skipping autoupgrade";
78 log(sCtx, logMessage, errorLog, null);
79 return;
80 }
81 try {
82 connection = dataSource.getConnection();
83 if (connection != null) {
84
85 connection.setAutoCommit(false);
86 }
87 } catch (Exception e) {
88
89 String logMessage = "unable to create connection, skipping autoupgrade";
90 log(sCtx, logMessage, errorLog, e);
91 return;
92 }
93 ScriptGrammar grammar = new ScriptGrammarManager().getGrammar(connection);
94 ResourceReaderFactory factory = new ClassLoaderReaderFactory("database/upgrade/" + grammar.name());
95 if (grammar == null) {
96 String logMessage = "" + connection + " does not use a supported database driver";
97 log(sCtx, logMessage, errorLog, null);
98 throw new RuntimeException(logMessage);
99 }
100 DeploymentRunner runner = DeploymentRunnerFactory.getInstance().getUpgradeRunner(factory,
101 connection,
102 grammar,
103 new IDSStartFolderRetriever(connection),
104 new IDSCompletedScriptRetriever(connection));
105 runner.addRunLogger(new IDSRunLogger(connection, dbversion));
106 runner.addRunLogger(this);
107 runner.run("upgrade.drv");
108 if (scriptsExecuted == 0) {
109 log(sCtx, "no upgrades necessary", errorLog, null);
110 } else {
111 log(sCtx, "executed " + scriptsExecuted + " scripts", errorLog, null);
112 }
113
114 } catch (Exception e) {
115 if (errorLog != null) {
116 try {
117 String logMessage = "unable to execute upgrade: " + e.getMessage();
118 log(sCtx, logMessage, errorLog, null);
119 } catch (IOException ignored) {
120
121 }
122 }
123 throw new RuntimeException(e);
124 } finally {
125 if (connection != null) {
126 try {
127 connection.close();
128 } catch (SQLException e) {
129
130 }
131 }
132 if (errorLog != null) {
133 try {
134 errorLog.close();
135 } catch (IOException e) {
136
137 }
138 }
139 }
140 }
141
142 public void logRunScript(String script, long time) throws Exception {
143 String logMessage = "executed " + script + " (" + time + "ms)";
144 log(sCtx, logMessage, errorLog, null);
145 scriptsExecuted++;
146 }
147
148 public void logRunComplete(String group, long time) throws Exception {
149 String logMessage = "completed execution of available scripts in " + group + " (" + time + "ms)";
150 log(sCtx, logMessage, errorLog, null);
151 }
152
153 public void logRunFailure(String group, String script, Exception e) throws Exception {
154 String logMessage = "failed to execute " + group + "/" + script;
155 log(sCtx, logMessage, errorLog, e);
156 throw new RuntimeException(logMessage, e);
157 }
158
159 private void log(final ServletContext sCtx, String message, final FileWriter errorLog, Exception e) throws IOException {
160 Throwable t = (e == null) ? null : e.fillInStackTrace();
161 if (t != null) {
162 sCtx.log(message, t);
163 } else {
164 sCtx.log(message);
165 }
166 if (errorLog != null) {
167 if (t != null) {
168 errorLog.write(message + "\n" + t.toString() + "\n");
169 } else {
170 errorLog.write(message + "\n");
171 }
172 errorLog.flush();
173 }
174 }
175
176 /**
177 *
178 * @param sCtx
179 * @return
180 * @todo defer creation of file until actual writing error
181 */
182 private FileWriter getErrorLogFileWriter(ServletContext sCtx) {
183 FileWriter errorLog = null;
184 String errorLogFormat = sCtx.getInitParameter("error.log");
185 if (errorLogFormat == null) {
186 sCtx.log("dbupgrade log file undefined, using servlet context log only");
187 } else {
188 try {
189 String fileName = PropertiesMessageFormat.format(errorLogFormat, System.getProperties());
190 File file = new File(fileName);
191 file.getParentFile().mkdirs();
192 errorLog = new FileWriter(file, true);
193 } catch (Exception e) {
194 sCtx.log("unable to create log file from " + errorLogFormat, e);
195 }
196 }
197 return errorLog;
198 }
199
200 /**
201 * does nothing; required for ServletContextListener implementation
202 */
203 public void contextDestroyed(ServletContextEvent event) {
204 }
205
206
207 static class PropertiesMessageFormat {
208 static String format(String format, Properties properties) {
209 Pattern p = Pattern.compile("\\{([^\\{\\}\\,]*)[\\}|\\,]");
210 Matcher m = p.matcher(format);
211 StringBuffer buf = new StringBuffer();
212 ArrayList args = new ArrayList();
213 while (m.find()) {
214 int location = args.size();
215 String match = m.group(0);
216 String variable = m.group(1);
217 String replacement = "{" + location + (match.endsWith(",") ? "," : "}");
218 args.add(getReplacement(variable, properties));
219 m.appendReplacement(buf, replacement);
220 }
221 m.appendTail(buf);
222 return MessageFormat.format(buf.toString(), args.toArray());
223 }
224
225 static Object getReplacement(String variable, Properties properties) {
226 Object replacement = properties.getProperty(variable);
227 if (replacement == null) {
228 if (variable.equals("current.time")) {
229 replacement = new Date();
230 } else {
231 replacement = "{" + variable + "}";
232 }
233 }
234 return replacement;
235 }
236 }
237
238 }