View Javadoc
1   package fr.ifremer.quadrige3.synchro.server.service.technical;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 Synchro server
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  
27  import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
28  import fr.ifremer.quadrige3.core.config.QuadrigeConfiguration;
29  import fr.ifremer.quadrige3.core.dao.technical.Daos;
30  import fr.ifremer.quadrige3.core.dao.technical.ZipUtils;
31  import fr.ifremer.quadrige3.synchro.server.config.SynchroServerConfiguration;
32  import fr.ifremer.quadrige3.synchro.service.doc.DocSynchroService;
33  import org.apache.commons.io.FileUtils;
34  import org.apache.commons.io.FilenameUtils;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.hibernate.cfg.Environment;
38  import org.nuiton.i18n.I18n;
39  import org.nuiton.version.Version;
40  import org.springframework.beans.factory.InitializingBean;
41  import org.springframework.beans.factory.annotation.Autowired;
42  import org.springframework.stereotype.Service;
43  
44  import java.io.File;
45  import java.io.IOException;
46  import java.sql.SQLException;
47  import java.util.Properties;
48  
49  import static org.nuiton.i18n.I18n.t;
50  
51  @Service("databaseSchemaService")
52  public class DatabaseSchemaServiceImpl extends fr.ifremer.quadrige3.core.service.DatabaseSchemaServiceImpl implements DatabaseSchemaService, InitializingBean {
53  
54      private static final Log log = LogFactory.getLog(DatabaseSchemaServiceImpl.class);
55  
56      private File emptyDbFileCache;
57  
58      private File tableRelationFileCache;
59  
60      @Autowired
61      protected SynchroServerConfiguration synchroServerConfig;
62  
63      @Autowired
64      protected DocSynchroService docSynchroService;
65  
66      @Autowired
67      public DatabaseSchemaServiceImpl(QuadrigeConfiguration config) {
68          this.config = config;
69          this.emptyDbFileCache = null;
70      }
71  
72      @Override
73      public void afterPropertiesSet() {
74          // Fill the cache at startup
75          initDbFileCache();
76      }
77  
78      @Override
79      public void createEmptyDb(File dbDirectory) {
80          // Empty DB not exists: ask for creation
81          if (emptyDbFileCache == null || !emptyDbFileCache.exists()) {
82              // File could have been deleted manually : so recreate it !
83              emptyDbFileCache = null;
84              initDbFileCache();
85  
86              // If file still not exists: stop here
87              if (emptyDbFileCache == null || !emptyDbFileCache.exists()) {
88                  throw new QuadrigeTechnicalException(t("quadrige3.synchro.server.import.newDatabase.noEmptyZipFile.error", emptyDbFileCache.getPath()));
89              }
90          }
91  
92          try {
93              ZipUtils.uncompressFileToPath(emptyDbFileCache.toPath(), dbDirectory.toPath(), false);
94          } catch (IOException e) {
95              // Log but continue (empty db will be recreated
96              log.error(t("quadrige3.synchro.server.import.newDatabase.unzipDirectory.error", config.getVersion().toString()), e);
97          }
98      }
99  
100     @Override
101     public File getEmptyDbFile() {
102         return emptyDbFileCache;
103     }
104 
105     @Override
106     public File getTableRelationFileCache() {
107         return tableRelationFileCache;
108     }
109 /* -- internal methods -- */
110 
111     /**
112      * Create a cached empty DB (as a zip file)
113      */
114     private void initDbFileCache() {
115 
116         if (emptyDbFileCache == null) {
117             emptyDbFileCache = createEmptyDbZipFile();
118             tableRelationFileCache = new File(emptyDbFileCache.getParentFile(), FilenameUtils.removeExtension(emptyDbFileCache.getName()) + ".json");
119         }
120         if (emptyDbFileCache.exists()) {
121             if (log.isInfoEnabled()) {
122                 log.info(I18n.t("quadrige3.synchro.server.init.emptyDb.skip", emptyDbFileCache.getPath()));
123             }
124             return;
125         }
126 
127         log.info(I18n.t("quadrige3.synchro.server.init.emptyDb.task", emptyDbFileCache.getPath()));
128 
129         // Create the DB into a temp directory
130         File emptyDbDirectory = new File(config.getTempDirectory(), String.valueOf(System.currentTimeMillis()));
131         generateAndUpdateNewTemporaryDb(emptyDbDirectory);
132 
133         // Create a zip
134         try {
135             if (emptyDbFileCache.exists()) {
136                 FileUtils.forceDelete(emptyDbFileCache);
137             }
138 
139             ZipUtils.compressFilesInPath(
140                     emptyDbDirectory.toPath(),
141                     emptyDbFileCache.toPath(),
142                     true // Delete the temp directory after compression
143             );
144         } catch (IOException e) {
145             // Log but continue : do care (another thread is maybe writing the file)
146             log.error(t("quadrige3.synchro.server.init.emptyDb.zipDirectory.error", config.getVersion().toString()), e);
147             throw new QuadrigeTechnicalException(t("quadrige3.synchro.server.init.emptyDb.zipDirectory.error", config.getVersion().toString()), e);
148         }
149     }
150 
151     private File createEmptyDbZipFile() {
152 
153         // Compute DB Zip file
154         Version quadrigeVersion = databaseSchemaDao.getSchemaVersionIfUpdate();
155         File synchroDirectory = config.getSynchronizationDirectory();
156 
157         return new File(synchroDirectory, "db-" + quadrigeVersion.toString() + ".zip");
158     }
159 
160     private void generateAndUpdateNewTemporaryDb(File emptyDbDirectory) {
161 
162         Properties connectionProperties = synchroServerConfig.getClientConnectionProperties();
163         connectionProperties.setProperty(Environment.URL, Daos.getJdbcUrl(emptyDbDirectory, config.getDbName()));
164 
165         // empty db not exists (or error occur during unzip), so create a new one
166         try {
167             try {
168 
169                 databaseSchemaDao.generateNewDb(emptyDbDirectory, false, null, connectionProperties, true/*temporary DB*/);
170 
171                 // Update the schema (will insert values into SYSTEM_VERSION)
172                 databaseSchemaDao.updateSchema(connectionProperties);
173 
174                 // Read all metadata and create table relations file
175                 docSynchroService.createTableRelationsFile(connectionProperties, tableRelationFileCache);
176 
177             } catch (Throwable e) {
178                 throw new QuadrigeTechnicalException(t("quadrige3.synchro.server.init.emptyDb.updateSchema.error"), e);
179             }
180         } finally {
181             closeSilently(connectionProperties);
182         }
183     }
184 
185     private void closeSilently(Properties connectionProperties) {
186         try {
187             Daos.shutdownDatabase(connectionProperties);
188         } catch (SQLException e) {
189             // Silent !
190         }
191     }
192 }