View Javadoc

1   /*
2    * symmetric is an open source database synchronization solution.
3    *   
4    * Copyright (C) Chris Henson <chenson42@users.sourceforge.net>
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 3 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, see
18   * <http://www.gnu.org/licenses/>.
19   */
20  package org.jumpmind.symmetric;
21  
22  import java.io.BufferedReader;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.FileWriter;
27  import java.io.IOException;
28  import java.io.InputStreamReader;
29  import java.net.MalformedURLException;
30  import java.nio.charset.Charset;
31  import java.sql.Connection;
32  
33  import org.apache.commons.cli.CommandLine;
34  import org.apache.commons.cli.CommandLineParser;
35  import org.apache.commons.cli.HelpFormatter;
36  import org.apache.commons.cli.Options;
37  import org.apache.commons.cli.ParseException;
38  import org.apache.commons.cli.PosixParser;
39  import org.apache.commons.dbcp.BasicDataSource;
40  import org.apache.commons.lang.exception.ExceptionUtils;
41  import org.apache.ddlutils.Platform;
42  import org.apache.ddlutils.io.DatabaseIO;
43  import org.apache.ddlutils.model.Database;
44  import org.jumpmind.symmetric.common.Constants;
45  import org.jumpmind.symmetric.db.IDbDialect;
46  import org.jumpmind.symmetric.db.SqlScript;
47  import org.jumpmind.symmetric.service.IBootstrapService;
48  import org.jumpmind.symmetric.service.IDataExtractorService;
49  import org.jumpmind.symmetric.service.IDataLoaderService;
50  import org.jumpmind.symmetric.service.IDataService;
51  import org.jumpmind.symmetric.service.IPurgeService;
52  import org.jumpmind.symmetric.service.IRegistrationService;
53  import org.jumpmind.symmetric.transport.IOutgoingTransport;
54  import org.jumpmind.symmetric.transport.internal.InternalOutgoingTransport;
55  import org.springframework.context.ApplicationContext;
56  import org.springframework.context.support.ClassPathXmlApplicationContext;
57  
58  /***
59   * Run symmetric utilities and/or launch an embedded version of Symmetric. If
60   * you run this program without any arguments 'help' will print out.
61   */
62  public class SymmetricLauncher {
63  
64      private static final String OPTION_DUMP_BATCH = "dump-batch";
65  
66      private static final String OPTION_OPEN_REGISTRATION = "open-registration";
67  
68      private static final String OPTION_RELOAD_NODE = "reload-node";
69  
70      private static final String OPTION_AUTO_CREATE = "auto-create";
71  
72      private static final String OPTION_PORT_SERVER = "port";
73  
74      private static final String OPTION_DDL_GEN = "generate-config-dll";
75  
76      private static final String OPTION_PURGE = "purge";
77  
78      private static final String OPTION_RUN_DDL_XML = "run-ddl";
79  
80      private static final String OPTION_RUN_SQL = "run-sql";
81  
82      private static final String OPTION_PROPERTIES_GEN = "generate-default-properties";
83  
84      private static final String OPTION_PROPERTIES_FILE = "properties";
85  
86      private static final String OPTION_START_SERVER = "server";
87  
88      private static final String OPTION_LOAD_BATCH = "load-batch";
89  
90      private static final String OPTION_SKIP_DB_VALIDATION = "skip-db-validate";
91  
92      public static void main(String[] args) throws Exception {
93  
94          CommandLineParser parser = new PosixParser();
95          Options options = buildOptions();
96          try {
97              CommandLine line = parser.parse(options, args);
98  
99              int serverPort = 31415;
100 
101             if (line.hasOption(OPTION_PORT_SERVER)) {
102                 serverPort = new Integer(line
103                         .getOptionValue(OPTION_PORT_SERVER));
104             }
105 
106             if (line.hasOption(OPTION_PROPERTIES_GEN)) {
107                 generateDefaultProperties(line
108                         .getOptionValue(OPTION_PROPERTIES_GEN));
109                 return;
110             }
111 
112             // validate that block-size has been set
113             if (line.hasOption(OPTION_PROPERTIES_FILE)) {
114                 System.setProperty(Constants.OVERRIDE_PROPERTIES_FILE_1,
115                         "file:" + line.getOptionValue(OPTION_PROPERTIES_FILE));
116                 if (!new File(line.getOptionValue(OPTION_PROPERTIES_FILE))
117                         .exists()) {
118                     throw new ParseException(
119                             "Could not find the properties file specified: "
120                                     + line
121                                             .getOptionValue(OPTION_PROPERTIES_FILE));
122                 }
123 
124             }
125 
126             if (line.hasOption(OPTION_DDL_GEN)) {
127                 generateDDL(new SymmetricEngine(), line
128                         .getOptionValue(OPTION_DDL_GEN));
129                 return;
130             }
131 
132             if (line.hasOption(OPTION_PURGE)) {
133                 ((IPurgeService) new SymmetricEngine().getApplicationContext()
134                         .getBean(Constants.PURGE_SERVICE)).purge();
135                 return;
136             }
137 
138             if (line.hasOption(OPTION_OPEN_REGISTRATION)) {
139                 String arg = line.getOptionValue(OPTION_OPEN_REGISTRATION);
140                 openRegistration(new SymmetricEngine(), arg);
141                 System.out.println("Opened Registration for " + arg);
142                 return;
143             }
144 
145             if (line.hasOption(OPTION_RELOAD_NODE)) {
146                 String arg = line.getOptionValue(OPTION_RELOAD_NODE);
147                 String message = reloadNode(new SymmetricEngine(), arg);
148                 System.out.println(message);
149                 return;
150             }
151 
152             if (line.hasOption(OPTION_DUMP_BATCH)) {
153                 String arg = line.getOptionValue(OPTION_DUMP_BATCH);
154                 dumpBatch(new SymmetricEngine(), arg);
155                 return;
156             }
157 
158             if (line.hasOption(OPTION_AUTO_CREATE)) {
159                 autoCreateDatabase(new SymmetricEngine());
160                 return;
161             }
162 
163             if (line.hasOption(OPTION_RUN_DDL_XML)) {
164                 runDdlXml(new SymmetricEngine(), line
165                         .getOptionValue(OPTION_RUN_DDL_XML));
166                 return;
167             }
168 
169             if (line.hasOption(OPTION_RUN_SQL)) {
170                 runSql(new SymmetricEngine(), line
171                         .getOptionValue(OPTION_RUN_SQL));
172                 return;
173             }
174 
175             if (line.hasOption(OPTION_LOAD_BATCH)) {
176                 loadBatch(new SymmetricEngine(), line
177                         .getOptionValue(OPTION_LOAD_BATCH));
178             }
179 
180             if (line.hasOption(OPTION_START_SERVER)) {
181                 if (!line.hasOption(OPTION_SKIP_DB_VALIDATION)) {
182                     testConnection();
183                 }
184                 new SymmetricWebServer().start(serverPort);
185                 return;
186             }
187 
188             printHelp(options);
189 
190         } catch (ParseException exp) {
191             System.err.println(exp.getMessage());
192             printHelp(options);
193         } catch (Exception ex) {
194             System.err
195                     .println("-----------------------------------------------------------------------------------------------");
196             System.err
197                     .println("  An exception occurred.  Please see the following for details: ");
198             System.err
199                     .println("-----------------------------------------------------------------------------------------------");
200 
201             ExceptionUtils.printRootCauseStackTrace(ex, System.err);
202             System.err
203                     .println("-----------------------------------------------------------------------------------------------");
204             printHelp(options);
205         }
206     }
207 
208     private static void printHelp(Options options) {
209         new HelpFormatter().printHelp("sym", options);
210     }
211 
212     private static void testConnection() throws Exception {
213         ApplicationContext ctx = new ClassPathXmlApplicationContext(
214                 new String[] { "classpath:/symmetric-properties.xml",
215                         "classpath:/symmetric-database.xml" });
216         BasicDataSource ds = (BasicDataSource) ctx
217                 .getBean(Constants.DATA_SOURCE);
218         Connection c = ds.getConnection();
219         c.close();
220         ds.close();
221     }
222 
223     private static Options buildOptions() {
224         Options options = new Options();
225         options.addOption("S", OPTION_START_SERVER, false,
226                 "Start an embedded instance of symmetric.");
227         options
228                 .addOption("P", OPTION_PORT_SERVER, true,
229                         "Optionally pass in the HTTP port number to use for the server instance.");
230         options
231                 .addOption(
232                         "c",
233                         OPTION_DDL_GEN,
234                         true,
235                         "Output the DDL to create the symmetric tables.  Takes an argument of the name of the file to write the ddl to.");
236         options
237                 .addOption(
238                         "p",
239                         OPTION_PROPERTIES_FILE,
240                         true,
241                         "Takes an argument with the path to the properties file that will drive symmetric.  If this is not provided, symmetric will use defaults, then override with the first symmetric.properties in your classpath, then override with symmetric.properties values in your user.home directory.");
242         options
243                 .addOption("X", OPTION_PURGE, false,
244                         "Will simply run the purge process against the currently configured database.");
245         options
246                 .addOption(
247                         "g",
248                         OPTION_PROPERTIES_GEN,
249                         true,
250                         "Takes an argument with the path to a file which all the default overrideable properties will be written.");
251         options
252                 .addOption(
253                         "r",
254                         OPTION_RUN_DDL_XML,
255                         true,
256                         "Takes an argument of a DdlUtils xml file and applies it to the database configured in your symmetric properties file.");
257         options
258                 .addOption(
259                         "s",
260                         OPTION_RUN_SQL,
261                         true,
262                         "Takes an argument of a .sql file and runs it against the database configured in your symmetric properties file.");
263 
264         options
265                 .addOption("a", OPTION_AUTO_CREATE, false,
266                         "Attempts to create the symmetric tables in the configured database.");
267         options
268                 .addOption(
269                         "R",
270                         OPTION_OPEN_REGISTRATION,
271                         true,
272                         "Open registration for the passed in node group and external id.  Takes an argument of {groupId},{externalId}.");
273         options
274                 .addOption("l", OPTION_RELOAD_NODE, true,
275                         "Send an initial load of data to reload the passed in node id.");
276         options
277                 .addOption(
278                         "d",
279                         OPTION_DUMP_BATCH,
280                         true,
281                         "Print the contents of a batch out to the console.  Takes the batch id as an argument.");
282         options.addOption("b", OPTION_LOAD_BATCH, true,
283                 "Load the CSV contents of the specfied file.");
284         options
285                 .addOption(
286                         "i",
287                         OPTION_SKIP_DB_VALIDATION,
288                         false,
289                         "Don't test to see if the database connection is valid before starting the server.  Note that if the connection is invalid, then the server will continually try to connect if this is set.");
290         return options;
291     }
292 
293     private static void dumpBatch(SymmetricEngine engine, String batchId)
294             throws Exception {
295         IDataExtractorService dataExtractorService = (IDataExtractorService) engine
296                 .getApplicationContext().getBean(
297                         Constants.DATAEXTRACTOR_SERVICE);
298         IOutgoingTransport transport = new InternalOutgoingTransport(System.out);
299         dataExtractorService.extractBatchRange(transport, batchId, batchId);
300         transport.close();
301     }
302 
303     private static void loadBatch(SymmetricEngine engine, String fileName)
304             throws Exception {
305         IDataLoaderService service = (IDataLoaderService) engine
306                 .getApplicationContext().getBean(Constants.DATALOADER_SERVICE);
307         File file = new File(fileName);
308         if (file.exists() && file.isFile()) {
309             FileInputStream in = new FileInputStream(file);
310             service.loadData(in, System.out);
311             System.out.flush();
312             in.close();
313 
314         } else {
315             throw new FileNotFoundException("Could not find " + fileName);
316         }
317     }
318 
319     private static void openRegistration(SymmetricEngine engine, String argument) {
320         argument = argument.replace('\"', ' ');
321         int index = argument.trim().indexOf(",");
322         if (index < 0) {
323             throw new IllegalArgumentException(
324                     "Check the argument you passed in.  --"
325                             + OPTION_OPEN_REGISTRATION
326                             + " takes an argument of {groupId},{externalId}");
327         }
328         String nodeGroupId = argument.substring(0, index).trim();
329         String externalId = argument.substring(index + 1).trim();
330         IRegistrationService registrationService = (IRegistrationService) engine
331                 .getApplicationContext()
332                 .getBean(Constants.REGISTRATION_SERVICE);
333         registrationService.openRegistration(nodeGroupId, externalId);
334     }
335 
336     private static String reloadNode(SymmetricEngine engine, String argument) {
337         IDataService dataService = (IDataService) engine
338                 .getApplicationContext().getBean(Constants.DATA_SERVICE);
339         return dataService.reloadNode(argument);
340     }
341 
342     private static void generateDDL(SymmetricEngine engine, String fileName)
343             throws IOException {
344         File file = new File(fileName);
345         file.getParentFile().mkdirs();
346         FileWriter os = new FileWriter(file, false);
347         os.write(((IDbDialect) engine.getApplicationContext().getBean(
348                 Constants.DB_DIALECT)).getCreateSymmetricDDL());
349         os.close();
350     }
351 
352     private static void generateDefaultProperties(String fileName)
353             throws IOException {
354         File file = new File(fileName);
355         file.getParentFile().mkdirs();
356         BufferedReader is = new BufferedReader(new InputStreamReader(
357                 SymmetricLauncher.class
358                         .getResourceAsStream("/symmetric-default.properties"),
359                 Charset.defaultCharset()));
360         FileWriter os = new FileWriter(file, false);
361         String line = is.readLine();
362         while (line != null) {
363             os.write(line);
364             os.write(System.getProperty("line.separator"));
365             line = is.readLine();
366         }
367         is.close();
368         os.close();
369     }
370 
371     private static void autoCreateDatabase(SymmetricEngine engine) {
372         IBootstrapService bootstrapService = (IBootstrapService) engine
373                 .getApplicationContext().getBean(Constants.BOOTSTRAP_SERVICE);
374         bootstrapService.setupDatabase(true);
375     }
376 
377     private static void runDdlXml(SymmetricEngine engine, String fileName)
378             throws FileNotFoundException {
379         IDbDialect dialect = (IDbDialect) engine.getApplicationContext()
380                 .getBean(Constants.DB_DIALECT);
381         File file = new File(fileName);
382         if (file.exists() && file.isFile()) {
383             Platform pf = dialect.getPlatform();
384             Database db = new DatabaseIO().read(new File(fileName));
385             pf.createTables(db, false, true);
386         } else {
387             throw new FileNotFoundException("Could not find " + fileName);
388         }
389     }
390 
391     private static void runSql(SymmetricEngine engine, String fileName)
392             throws FileNotFoundException, MalformedURLException {
393         IDbDialect dialect = (IDbDialect) engine.getApplicationContext()
394                 .getBean(Constants.DB_DIALECT);
395         File file = new File(fileName);
396         if (file.exists() && file.isFile()) {
397             SqlScript script = new SqlScript(file.toURL(), dialect
398                     .getPlatform().getDataSource());
399             script.execute();
400         } else {
401             throw new FileNotFoundException("Could not find " + fileName);
402         }
403     }
404 
405 }