PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/atlassian/uwc/exporters/JotspotExporter.java

https://bitbucket.org/atlassianlabs/universal-wiki-connector
Java | 486 lines | 342 code | 37 blank | 107 comment | 45 complexity | 270bbc5f544e78bdb2bf40e378af2b85 MD5 | raw file
  1. package com.atlassian.uwc.exporters;
  2. import java.io.BufferedInputStream;
  3. import java.io.BufferedOutputStream;
  4. import java.io.File;
  5. import java.io.FileInputStream;
  6. import java.io.FileNotFoundException;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import java.util.Collections;
  10. import java.util.Comparator;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. import java.util.Properties;
  14. import java.util.Set;
  15. import java.util.Stack;
  16. import java.util.TreeMap;
  17. import java.util.Vector;
  18. import java.util.regex.Matcher;
  19. import java.util.regex.Pattern;
  20. import java.util.zip.ZipEntry;
  21. import java.util.zip.ZipInputStream;
  22. import org.apache.log4j.Logger;
  23. import org.apache.log4j.PropertyConfigurator;
  24. /**
  25. * unzips the Jotspot export zip file, and deletes unnecessary files.
  26. * Input zip and Output directory are set in exporter.jotspot.properties.
  27. * Optionally, a comma delimited list of protected files and directories
  28. * can be set in the exporter.jotspot.properties
  29. * @author Laura Kolker
  30. */
  31. public class JotspotExporter implements Exporter {
  32. //CONSTANTS
  33. //properties constants
  34. public static final String DEFAULT_PROPERTIES_LOCATION = "exporter.jotspot.properties";
  35. public static final String EXPORTER_PROPERTIES_INFILE = "exported.zipfile.location";
  36. public static final String EXPORTER_PROPERTIES_OUTPUTDIR = "exported.output.dir";
  37. public static final String EXPORTER_PROPERTIES_PROTECTED = "exported.protected.directories"; //optional comma delim list
  38. public static final String[] DEFAULT_EXCLUDES = {
  39. "Calendar",
  40. "Calendar.xml",
  41. "FileCabinet",
  42. "FileCabinet.xml",
  43. "GroupManagement.xml",
  44. "Lib",
  45. "Lib.xml",
  46. "META-INF",
  47. "MasterIndex.xml",
  48. "PhotoGallery",
  49. "PhotoGallery.xml",
  50. "Spreadsheet",
  51. "Spreadsheet.xml",
  52. "ToDoList",
  53. "ToDoList.xml",
  54. "WikiMarkupTips.xml",
  55. "_Admin",
  56. "_Admin.xml",
  57. "_deleted",
  58. "System/Actions",
  59. "System/Actions.xml",
  60. "System/Async",
  61. "System/Async.xml",
  62. "System/CSS",
  63. "System/CSS.xml",
  64. "System/ColorSchemes",
  65. "System/ColorSchemes.xml",
  66. "System/Actions",
  67. "System/Actions.xml",
  68. "System/Async",
  69. "System/Async.xml",
  70. "System/CSS",
  71. "System/CSS.xml",
  72. "System/ColorSchemes",
  73. "System/ColorSchemes.xml",
  74. "System/Defaults.xml",
  75. "System/Errors",
  76. "System/Errors.xml",
  77. "System/Forms",
  78. "System/Forms.xml",
  79. "System/Includes",
  80. "System/Includes.xml",
  81. "System/JotPlan",
  82. "System/JotPlan.xml",
  83. "System/Locale",
  84. "System/Locale.xml",
  85. "System/Packages",
  86. "System/Packages.xml",
  87. "System/Pages",
  88. "System/Pages.xml",
  89. "System/Plugins",
  90. "System/Plugins.xml",
  91. "System/SVG",
  92. "System/SVG.xml",
  93. "System/SystemShared",
  94. "System/SystemShared.xml",
  95. "System/Themes",
  96. "System/Themes.xml",
  97. "System/ToDoList",
  98. "System/ToDoList.xml",
  99. "System/WebspaceConfig.xml",
  100. "System/WebspaceEmailConfig",
  101. "System/WebspaceEmailConfig.xml"
  102. };
  103. //status constants
  104. private static final String STATUS_OK = "OK";
  105. private static final String STATUS_ERROR = "ERROR";
  106. private static final String STATUS_CANCELLED = "CANCELLED";
  107. //log4j
  108. private Logger log = Logger.getLogger(this.getClass());
  109. //status
  110. private String status = STATUS_OK;
  111. //cancel
  112. private boolean running = false;
  113. /**
  114. * entry method if we use this class as an App.
  115. * properties must be in exporter.jotspot.properties
  116. * @param args
  117. */
  118. public static void main(String[] args) {
  119. PropertyConfigurator.configure("log4j.properties");
  120. JotspotExporter exp = new JotspotExporter();
  121. Map propsMap = exp.getProperties(DEFAULT_PROPERTIES_LOCATION);
  122. exp.export(propsMap);
  123. }
  124. /**
  125. * default properties grabber. used by main when this class is used as an app.
  126. * @param filename path to properties file
  127. * @return map of properties from properties file
  128. */
  129. private Map getProperties(String filename) {
  130. Properties props = new Properties();
  131. Map<String, String> propsMap = new HashMap<String, String>();
  132. filename = "conf/"+filename;
  133. try {
  134. props.load(new FileInputStream(filename));
  135. propsMap.put(EXPORTER_PROPERTIES_INFILE, props.getProperty(EXPORTER_PROPERTIES_INFILE));
  136. propsMap.put(EXPORTER_PROPERTIES_OUTPUTDIR, props.getProperty(EXPORTER_PROPERTIES_OUTPUTDIR));
  137. propsMap.put(EXPORTER_PROPERTIES_PROTECTED, props.getProperty(EXPORTER_PROPERTIES_PROTECTED));
  138. } catch (FileNotFoundException e) {
  139. log.error("Cannot find properties file");
  140. e.printStackTrace();
  141. } catch (IOException e) {
  142. log.error("Cannot load properties file");
  143. e.printStackTrace();
  144. }
  145. return propsMap;
  146. }
  147. /**
  148. * exports the Mediawiki database described in the given properties
  149. * to text files that will be written to the output directory
  150. * @param properties Map of properties. Must contain keys: databaseName,
  151. * dbUrl, jdbc.driver.class, login, password, output. See example file
  152. * exporter.mediawiki.properties
  153. */
  154. public void export(Map properties) {
  155. this.running = true;
  156. //set up log4j
  157. PropertyConfigurator.configure("log4j.properties");
  158. //setup database connection
  159. log.info("Exporting Jotspot...");
  160. if (missingRequiredProperties(properties)) {
  161. this.status = STATUS_ERROR + ": required properties are not set. " +
  162. "Please configure " + DEFAULT_PROPERTIES_LOCATION;
  163. log.error(this.status);
  164. return;
  165. }
  166. //prepare infile
  167. File infile = getInfile(properties);
  168. if (infile == null) return;
  169. //prepare output directory
  170. File outdir = getOutdirectory(properties);
  171. if (outdir == null) return;
  172. //prepare include/exclude map
  173. TreeMap<String, Boolean> excludeInclude = getIncludeExcludeMap(properties);
  174. //unzip infile to output directory
  175. expand(infile, outdir, excludeInclude);
  176. if (!STATUS_OK.equals(this.status)) {
  177. log.error("Export Status: " + this.status);
  178. return;
  179. }
  180. log.info("Export Complete.");
  181. this.running = false;
  182. }
  183. /**
  184. * @param properties String Map containing all export properties
  185. * @return true if any required property is null or empty.
  186. */
  187. private boolean missingRequiredProperties(Map<String, String> properties) {
  188. String in = properties.get(EXPORTER_PROPERTIES_INFILE);
  189. String out = properties.get(EXPORTER_PROPERTIES_OUTPUTDIR);
  190. return (in == null || out == null | "".equals(in) || "".equals(out));
  191. }
  192. /**
  193. * @param properties String Map with key-value pair, where a key = EXPORTERS_PROPERTIES_INFILE
  194. * @return File object associated with EXPORTER_PROPERTIES_INFILE, or null if no such non-directory
  195. * object exists
  196. */
  197. private File getInfile(Map<String, String> properties) {
  198. if (!this.running) {
  199. this.status = STATUS_CANCELLED;
  200. return null;
  201. }
  202. String in = properties.get(EXPORTER_PROPERTIES_INFILE);
  203. File file = new File(in);
  204. if (!file.exists() || file.isDirectory()) {
  205. this.status = STATUS_ERROR + ": Zip file does not exist or is a directory: " + in;
  206. log.error(this.status);
  207. return null;
  208. }
  209. log.info("Zip file: " + in);
  210. return file;
  211. }
  212. /**
  213. * @param properties String Map with key-value pair, where a key = EXPORTERS_PROPERTIES_OUTPUTDIR
  214. * @return File object representing empty directory associated with EXPORTER_PROPERTIES_OUTPUTDIR.
  215. * If dir does not exist, it will be created.
  216. * If dir already exists and is a directory, the directory will be deleted and recreated.
  217. * If dir already exists but is not a directory, this method will return null.
  218. */
  219. private File getOutdirectory(Map<String, String> properties) {
  220. if (!this.running) {
  221. this.status = STATUS_CANCELLED;
  222. return null;
  223. }
  224. String out = properties.get(EXPORTER_PROPERTIES_OUTPUTDIR);
  225. File file = new File(out);
  226. if (!file.exists()) {
  227. log.info("Creating output directory: " + out);
  228. file.mkdir();
  229. }
  230. else if (file.isDirectory()) {
  231. deleteDir(file);
  232. file.mkdir();
  233. log.info("Cleaning and creating output directory:" + out);
  234. }
  235. else {
  236. this.status = STATUS_ERROR + ": requested output directory (" +
  237. out +
  238. ") is currently a file. Please choose a directory. ";
  239. log.error(this.status);
  240. return null;
  241. }
  242. return file;
  243. }
  244. /**
  245. * @param properties String Map with key-value pair, where a key = EXPORTERS_PROPERTIES_PROTECTED
  246. * @return Map String->Boolean key value pairs
  247. * keys: file paths
  248. * value: True if should be included (is protected), False if it should be excluded
  249. */
  250. private TreeMap<String, Boolean> getIncludeExcludeMap(Map<String, String> properties) {
  251. if (!this.running) {
  252. this.status = STATUS_CANCELLED;
  253. return null;
  254. }
  255. TreeMap<String, Boolean> ieMap = new TreeMap<String, Boolean>();
  256. //add default excludes
  257. for (String excludable : DEFAULT_EXCLUDES) {
  258. if (!this.running) {
  259. this.status = STATUS_CANCELLED;
  260. return null;
  261. }
  262. ieMap.put(excludable, false);
  263. }
  264. //add properties set includes (Might trump default excludes.)
  265. String protectedPaths = properties.get(EXPORTER_PROPERTIES_PROTECTED);
  266. if (protectedPaths != null && !"".equals(protectedPaths) ) {
  267. String[] paths = protectedPaths.split(", ");
  268. for (String includable : paths) {
  269. if (!this.running) {
  270. this.status = STATUS_CANCELLED;
  271. return null;
  272. }
  273. ieMap.put(includable, true);
  274. }
  275. }
  276. return ieMap;
  277. }
  278. /**
  279. * unzip, leaving out unwanted Jotspot directories, but keeping
  280. * protected directories
  281. * @param infile from here
  282. * @param outdir to there
  283. * @param excludeInclude map of String->Boolean objects.
  284. * keys are filenames/directories
  285. * values are True if we include it, False if we exclude it
  286. */
  287. private void expand(File infile, File outdir,
  288. TreeMap<String, Boolean> excludeInclude) {
  289. if (!this.running) {
  290. this.status = STATUS_CANCELLED;
  291. return;
  292. }
  293. ZipInputStream zip = null;
  294. int BUFFER = 512;
  295. try {
  296. FileInputStream inStream = new FileInputStream(infile);
  297. BufferedOutputStream zipout = null;
  298. zip = new ZipInputStream(new BufferedInputStream(inStream));
  299. ZipEntry entry;
  300. while ((entry = zip.getNextEntry()) != null) {
  301. if (!this.running) {
  302. this.status = STATUS_CANCELLED;
  303. break; //break, not return, so we can close the file stream
  304. }
  305. if (exclude(entry.getName(), excludeInclude))
  306. continue;
  307. int count;
  308. byte data[] = new byte[BUFFER];
  309. createAnyNecessaryDirectories(outdir, entry.getName());
  310. String entryPath = outdir + "/" + entry.getName();
  311. FileOutputStream outStream = new FileOutputStream(entryPath);
  312. zipout = new BufferedOutputStream(outStream, BUFFER);
  313. while ((count = zip.read(data, 0, BUFFER)) != -1) {
  314. zipout.write(data, 0, count);
  315. }
  316. zipout.flush();
  317. zipout.close();
  318. }
  319. zip.close();
  320. } catch (FileNotFoundException e) {
  321. this.status = STATUS_ERROR + ": Could not expand file: " + infile.getName();
  322. log.error(this.status);
  323. e.printStackTrace();
  324. return;
  325. } catch (IOException e) {
  326. e.printStackTrace();
  327. }
  328. }
  329. /**
  330. * @param path file path
  331. * @param excludeInclude keys should be paths (could be directories), values should be
  332. * booleans: true for include, false for exclude
  333. * @return true if the given path should be excluded. false, if it should be included.
  334. */
  335. private boolean exclude(String path, TreeMap<String, Boolean> excludeInclude) {
  336. // look for keys that are equivalent first
  337. if (excludeInclude.containsKey(path)) {
  338. return !excludeInclude.get(path);
  339. }
  340. // sort the [ex/in]clude keyset by length
  341. // important for handling deep paths
  342. Set<String> keySet = (Set<String>) excludeInclude.keySet();
  343. Vector<String> keys = new Vector<String>();
  344. keys.addAll(keySet);
  345. LongestItems longestFirst = new LongestItems();
  346. Collections.sort(keys, longestFirst);
  347. // check for directory include/excludes
  348. for (String key : keys) {
  349. if (path.startsWith(key)) {
  350. return !excludeInclude.get(key);
  351. }
  352. }
  353. // include by default
  354. return false;
  355. }
  356. /**
  357. * creates any necessary directories from name in outdir
  358. * @param outdir existing directory
  359. * @param name filepath that shoudl go in outdir eventually
  360. */
  361. private void createAnyNecessaryDirectories(File outdir, String name) {
  362. String completePath = outdir.getAbsolutePath() + "/" + name;
  363. Stack<File> candidates = new Stack<File>();
  364. //look at each parent dir (from bottom up) for existence
  365. while (!"".equals(completePath)) {
  366. completePath = getLowestDir(completePath);
  367. File testFile = new File(completePath);
  368. if (!testFile.exists()) {
  369. candidates.push(testFile);
  370. }
  371. else {
  372. break;
  373. }
  374. }
  375. //non existing parent directories are created
  376. while (!candidates.isEmpty()) {
  377. File tmpFile = candidates.pop();
  378. tmpFile.mkdir();
  379. }
  380. }
  381. Pattern lowestPath = Pattern.compile("(.*)\\/[^\\/]*");
  382. /**
  383. * @param path
  384. * @return the deepest parent directory.
  385. * Example:
  386. * path = "Some/Directory/file.txt
  387. * return = "Some/Directory"
  388. */
  389. private String getLowestDir(String path) {
  390. Matcher pathFinder = lowestPath.matcher(path);
  391. if (pathFinder.lookingAt()) {
  392. String parent = pathFinder.group(1);
  393. return parent;
  394. }
  395. return path;
  396. }
  397. /**
  398. * @return returns export status
  399. */
  400. public String getStatus() {
  401. return status;
  402. }
  403. /**
  404. * deletes the given file. This method is used recursively.
  405. * @param file can be a directory or a file. Directory does not have to be empty.
  406. */
  407. private void deleteDir(File file) {
  408. //if file doesn't exist (shouldn't happen), just exit
  409. if (!file.exists()) return;
  410. String name = "";
  411. try {
  412. name = file.getCanonicalPath();
  413. } catch (IOException e) {
  414. log.error("Problem while deleting directory. No filename!");
  415. e.printStackTrace();
  416. }
  417. //delete the file
  418. if (file.delete()) {
  419. log.debug("Deleting " + name);
  420. return;
  421. }
  422. else { // or delete the directory
  423. File[] files = file.listFiles();
  424. for (File f : files) {
  425. deleteDir(f);
  426. }
  427. file.delete();
  428. log.debug("Deleting dir: " + name);
  429. }
  430. }
  431. public void cancel() {
  432. String message = "Jotspot Exporter - Sending Cancel Signal";
  433. log.debug(message);
  434. this.running = false;
  435. }
  436. /**
  437. * @author Laura Kolker
  438. * Comparator for sorting by string length. Lengthier strings first.
  439. */
  440. public class LongestItems implements Comparator {
  441. public int compare(Object a, Object b) {
  442. String aStr = (String) a;
  443. int aLen = aStr.length();
  444. String bStr = (String) b;
  445. int bLen = bStr.length();
  446. return (bLen - aLen);
  447. }
  448. }
  449. }