1 package fr.ifremer.quadrige3.core.service.persistence;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import fr.ifremer.quadrige3.core.ProgressionCoreModel;
27 import fr.ifremer.quadrige3.core.config.QuadrigeConfiguration;
28 import fr.ifremer.quadrige3.core.config.QuadrigeCoreConfiguration;
29 import fr.ifremer.quadrige3.core.dao.technical.Assert;
30 import fr.ifremer.quadrige3.core.dao.technical.Daos;
31 import fr.ifremer.quadrige3.core.dao.technical.Files;
32 import fr.ifremer.quadrige3.core.dao.technical.ZipUtils;
33 import fr.ifremer.quadrige3.core.exception.QuadrigeBusinessException;
34 import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
35 import fr.ifremer.quadrige3.core.service.ClientServiceLocator;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.nuiton.jaxx.application.ApplicationIOUtil;
40 import org.nuiton.jaxx.application.ApplicationTechnicalException;
41
42 import javax.sql.DataSource;
43 import java.io.File;
44 import java.io.IOException;
45 import java.nio.file.FileVisitResult;
46 import java.nio.file.Path;
47 import java.nio.file.Paths;
48 import java.nio.file.SimpleFileVisitor;
49 import java.nio.file.attribute.BasicFileAttributes;
50 import java.time.LocalDate;
51 import java.time.format.DateTimeFormatter;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.stream.Collectors;
55 import java.util.zip.ZipEntry;
56
57 import static org.nuiton.i18n.I18n.t;
58
59
60
61
62
63
64 public class PersistenceServiceHelper {
65
66 private static final Log LOG = LogFactory.getLog(PersistenceServiceHelper.class);
67
68 private static final DateTimeFormatter BACKUP_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
69
70 private static final String BACKUP_DIRECTORY_FORMAT = "%s-%s-%s";
71
72
73
74
75
76
77
78
79
80
81
82
83 public static void backupDatabase(Path file, ProgressionCoreModel progressionModel) {
84 Assert.notNull(file);
85 DataSource dataSource = ClientServiceLocator.instance().getService("dataSource", DataSource.class);
86 Assert.notNull(dataSource);
87 QuadrigeCoreConfiguration config = QuadrigeCoreConfiguration.getInstance();
88 Assert.notNull(config);
89
90
91 String tempDirName = String.format(
92 BACKUP_DIRECTORY_FORMAT,
93 config.getApplicationName(),
94 config.getVersion(),
95 BACKUP_DATE_FORMAT.format(LocalDate.now()));
96
97 File structureDirectory = new File(config.newTempFile("backupDb"), tempDirName);
98
99 try {
100 ApplicationIOUtil.forceMkdir(structureDirectory,
101 t("quadrige3.error.create.directory", structureDirectory));
102
103 if (LOG.isDebugEnabled()) {
104 LOG.debug("Backup directory: " + structureDirectory);
105 }
106
107
108 String dbPath = structureDirectory.getAbsolutePath();
109 dbPath = StringUtils.appendIfMissing(dbPath, File.separator) + Daos.DB_DIRECTORY + File.separator;
110 String sql = "BACKUP DATABASE TO '" + dbPath + "' BLOCKING AS FILES";
111 try {
112 Daos.sqlUpdate(dataSource, sql);
113 } catch (Exception e) {
114 throw new ApplicationTechnicalException(t("quadrige3.service.persistence.copyDirectory.db.error"), e);
115 }
116
117
118 try {
119 progressionModel.setTotal(0);
120 Files.copyFile(
121 config.getDbDirectory().toPath().resolve(Daos.DB_VERSION_FILE),
122 Paths.get(dbPath).resolve(Daos.DB_VERSION_FILE),
123 progressionModel
124 );
125 } catch (IOException e) {
126 throw new QuadrigeTechnicalException(t("quadrige3.service.persistence.copyFile.error", Daos.DB_VERSION_FILE), e);
127 }
128
129
130 backupDirectory(config.getDbAttachmentDirectory().toPath(), structureDirectory.toPath(), progressionModel);
131
132 backupDirectory(config.getDbPhotoDirectory().toPath(), structureDirectory.toPath(), progressionModel);
133
134
135 config.getDbOtherDirectories().forEach(directory -> backupDirectory(directory.toPath(), structureDirectory.toPath(), progressionModel));
136
137
138 if (config.getSynchronizationDirectory() != null && config.getSynchronizationDirectory().isDirectory()) {
139 String synchroDirectoryName = config.getSynchronizationDirectory().getName();
140 final String[] currentFile = new String[1];
141 try {
142 progressionModel.setTotal(0);
143 java.nio.file.Files.walkFileTree(config.getSynchronizationDirectory().toPath(), new SimpleFileVisitor<Path>() {
144 @Override
145 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
146 if (file.getFileName().toString().equalsIgnoreCase(Daos.IMPORT_PROPERTIES_FILE)
147 || file.getFileName().toString().equalsIgnoreCase(Daos.EXPORT_PROPERTIES_FILE)) {
148 currentFile[0] = file.toAbsolutePath().toString();
149 Path target = structureDirectory.toPath().resolve(synchroDirectoryName).resolve(config.getSynchronizationDirectory().toPath().relativize(file));
150 Files.copyFile(file, target, progressionModel);
151 }
152 return FileVisitResult.CONTINUE;
153 }
154 });
155 } catch (IOException e) {
156 throw new ApplicationTechnicalException(t("quadrige3.service.persistence.copyFile.error", currentFile[0]), e);
157 }
158 }
159
160
161 try {
162 progressionModel.setTotal(0);
163 ZipUtils.compressFilesInPath(structureDirectory.toPath(), file, progressionModel, false, true);
164 } catch (IOException e) {
165 throw new QuadrigeTechnicalException(t("quadrige3.service.persistence.backupDb.zip.error", file), e);
166 }
167
168 } finally {
169
170
171 ApplicationIOUtil.forceDeleteOnExit(
172 structureDirectory,
173 t("quadrige3.service.persistence.backupDb.deleteTempDir.error", structureDirectory));
174 }
175
176 }
177
178 private static void backupDirectory(Path sourceDirectory, Path targetDirectory, ProgressionCoreModel progressionModel) {
179 if (sourceDirectory != null && java.nio.file.Files.isDirectory(sourceDirectory)) {
180 try {
181 if (progressionModel != null)
182 progressionModel.setTotal(0);
183 Files.copyDirectory(
184 sourceDirectory,
185 targetDirectory.resolve(sourceDirectory.getFileName().toString()),
186 progressionModel);
187 } catch (IOException e) {
188 throw new QuadrigeTechnicalException(t("quadrige3.service.persistence.copyDirectory.error", sourceDirectory), e);
189 }
190 }
191 }
192
193 public static void restoreDatabase(Path zipFile, ProgressionCoreModel progressionModel) {
194
195 QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
196 Assert.notNull(config);
197
198
199 Assert.notNull(zipFile);
200
201
202 String rootDirName = getRootDirectoryOfArchive(zipFile);
203
204 Path target = config.getDataDirectory().toPath();
205
206 if (LOG.isInfoEnabled()) {
207 LOG.info("Import db to " + target);
208 }
209
210 try {
211 progressionModel.setTotal(0);
212 ZipUtils.uncompressFileToPath(zipFile, target, rootDirName, progressionModel, false);
213 } catch (IOException e) {
214 throw new QuadrigeTechnicalException(t("quadrige3.service.persistence.restoreDb.extractFailed", zipFile), e);
215 }
216
217
218 migrateDbName();
219 }
220
221
222
223
224 public static void migrateDbName() {
225
226 QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
227 Assert.notNull(config);
228
229 Path dbDir = Paths.get(config.getDbDirectory().getAbsolutePath());
230 if (java.nio.file.Files.isDirectory(dbDir)) {
231 try {
232
233
234 for (Path oldDbFile : java.nio.file.Files.list(dbDir).filter(path -> path.getFileName().toString().startsWith("quadrige2")).collect(Collectors.toList())) {
235 String fileName = oldDbFile.getFileName().toString();
236 int extensionIndex = fileName.lastIndexOf(".");
237 String extension = extensionIndex != -1 ? fileName.substring(extensionIndex) : "";
238
239 java.nio.file.Files.move(oldDbFile, oldDbFile.resolveSibling(config.getDbName() + extension));
240 }
241 } catch (IOException e) {
242 throw new QuadrigeTechnicalException(t("quadrige3.service.persistence.migrateDbName.error"), e);
243 }
244 }
245
246 }
247
248 public static String getRootDirectoryOfArchive(Path file) {
249
250 QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
251 Assert.notNull(config);
252 Assert.notNull(file);
253
254 if (!java.nio.file.Files.exists(file)) {
255 throw new QuadrigeBusinessException(t("quadrige3.service.persistence.restoreDb.fileNotExist", file));
256 }
257
258 String rootDirectory = null;
259 boolean dbDirectoryFound = false;
260 boolean dbScriptFileFound = false;
261
262 try {
263 for (ZipEntry zipEntry : ZipUtils.getEntries(file)) {
264 if (zipEntry.isDirectory()) {
265
266 if (rootDirectory == null) {
267 rootDirectory = zipEntry.getName().substring(0, zipEntry.getName().indexOf("/"));
268 } else if (!zipEntry.getName().startsWith(rootDirectory)) {
269 throw new QuadrigeBusinessException(t("quadrige3.service.persistence.restoreDb.tooManyChildren"));
270 }
271
272 if (String.format("%s/%s/", rootDirectory, Daos.DB_DIRECTORY).equalsIgnoreCase(zipEntry.getName())) {
273 dbDirectoryFound = true;
274 }
275 } else {
276
277 if (String.format("%s/%s/%s.%s", rootDirectory, Daos.DB_DIRECTORY, config.getDbName(), "script").equalsIgnoreCase(zipEntry.getName())) {
278 dbScriptFileFound = true;
279 }
280 }
281 }
282 } catch (IOException e) {
283 throw new QuadrigeTechnicalException(t("quadrige3.service.persistence.restoreDb.readFailed", file), e);
284 }
285
286 if (!dbDirectoryFound)
287 throw new QuadrigeBusinessException(t("quadrige3.service.persistence.restoreDb.itemNotFound", file, Daos.DB_DIRECTORY));
288 if (!dbScriptFileFound)
289 throw new QuadrigeBusinessException(t("quadrige3.service.persistence.restoreDb.itemNotFound", file, String.format("%s.%s", config.getDbName(), "script")));
290
291 return rootDirectory;
292 }
293
294
295
296
297
298
299 public static void deleteDatabaseDirectory(ProgressionCoreModel progressionModel) {
300
301 QuadrigeCoreConfiguration config = QuadrigeCoreConfiguration.getInstance();
302 Assert.notNull(config);
303
304 List<Runnable> runnables = new ArrayList<>();
305 runnables.add(() -> {
306
307 File dbDirectory = config.getDbDirectory();
308 if (dbDirectory.isDirectory()) {
309 if (LOG.isInfoEnabled()) {
310 LOG.info("Delete previous database directory: " + dbDirectory);
311 }
312 Files.cleanDirectory(dbDirectory.toPath(), "Could not delete old db directory", progressionModel);
313 }
314 });
315
316 runnables.add(() -> {
317
318 File cacheDirectory = config.getCacheDirectory();
319 if (cacheDirectory.isDirectory()) {
320 if (LOG.isInfoEnabled()) {
321 LOG.info("Delete previous database cache directory: " + cacheDirectory);
322 }
323 Files.cleanDirectory(cacheDirectory.toPath(), "Could not delete old db cache directory", progressionModel);
324 }
325 });
326
327 runnables.add(() -> {
328
329 File synchroDirectory = config.getSynchronizationDirectory();
330 if (synchroDirectory.isDirectory()) {
331 if (LOG.isInfoEnabled()) {
332 LOG.info("Delete previous database data synchro directory: " + synchroDirectory);
333 }
334
335 Files.cleanDirectory(synchroDirectory.toPath(), "Could not delete old synchro directory", progressionModel);
336 }
337 });
338
339 runnables.add(() -> {
340
341 File measFileDirectory = config.getDbAttachmentDirectory();
342 if (measFileDirectory.isDirectory()) {
343 if (LOG.isInfoEnabled()) {
344 LOG.info("Delete previous database measurement files directory: " + measFileDirectory);
345 }
346
347 Files.cleanDirectory(measFileDirectory.toPath(), "Could not delete old measurement files directory", progressionModel);
348 }
349 });
350
351 runnables.add(() -> {
352
353 File photoDirectory = config.getDbPhotoDirectory();
354 if (photoDirectory.isDirectory()) {
355 if (LOG.isInfoEnabled()) {
356 LOG.info("Delete previous database photos directory: " + photoDirectory);
357 }
358
359 Files.cleanDirectory(photoDirectory.toPath(), "Could not delete old photos directory", progressionModel);
360 }
361 });
362
363 runnables.add(() -> {
364
365 config.getDbOtherDirectories().forEach(otherDirectory -> {
366 if (otherDirectory.isDirectory()) {
367 if (LOG.isInfoEnabled()) {
368 LOG.info("Delete previous database additional directory: " + otherDirectory);
369 }
370 Files.cleanDirectory(otherDirectory.toPath(), "Could not delete old additional directory: " + otherDirectory.getName(), progressionModel);
371 }
372 });
373 });
374
375 runnables.forEach(Runnable::run);
376
377 }
378
379 }