Increase code coverage overall
[policy/drools-pdp.git] / policy-management / src / main / java / org / onap / policy / drools / persistence / FileSystemPersistence.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2024 Nordix Foundation.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.drools.persistence;
23
24 import java.io.File;
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.nio.file.StandardCopyOption;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Comparator;
34 import java.util.List;
35 import java.util.Properties;
36 import java.util.function.BiPredicate;
37 import lombok.Getter;
38 import lombok.ToString;
39 import org.onap.policy.drools.properties.DroolsPropertyConstants;
40 import org.onap.policy.drools.utils.PropertyUtil;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * Properties based Persistence.
46  */
47 @Getter
48 @ToString
49 public class FileSystemPersistence implements SystemPersistence {
50
51     /**
52      * Properties file extension.
53      */
54     public static final String PROPERTIES_FILE_EXTENSION = ".properties";
55
56     /**
57      * Policy controllers suffix.
58      */
59     public static final String CONTROLLER_SUFFIX_IDENTIFIER = "-controller";
60
61     /**
62      * File Backup Suffix.
63      */
64     public static final String FILE_BACKUP_SUFFIX = ".bak";
65
66     /**
67      * Policy controller properties file suffix.
68      */
69     public static final String PROPERTIES_FILE_CONTROLLER_SUFFIX =
70             CONTROLLER_SUFFIX_IDENTIFIER + PROPERTIES_FILE_EXTENSION;
71
72     /**
73      * Topic configuration suffix.
74      */
75     public static final String TOPIC_SUFFIX_IDENTIFIER = "-topic";
76
77     /**
78      * Topic properties file suffix.
79      */
80     public static final String PROPERTIES_FILE_TOPIC_SUFFIX = TOPIC_SUFFIX_IDENTIFIER + PROPERTIES_FILE_EXTENSION;
81
82     /**
83      * Policy engine properties file name.
84      */
85     public static final String PROPERTIES_FILE_ENGINE = "engine" + PROPERTIES_FILE_EXTENSION;
86
87     public static final String HTTP_SERVER_SUFFIX_IDENTIFIER = "-http-server";
88     public static final String PROPERTIES_FILE_HTTP_SERVER_SUFFIX =
89             HTTP_SERVER_SUFFIX_IDENTIFIER + PROPERTIES_FILE_EXTENSION;
90
91     public static final String HTTP_CLIENT_SUFFIX_IDENTIFIER = "-http-client";
92     public static final String PROPERTIES_FILE_HTTP_CLIENT_SUFFIX =
93             HTTP_CLIENT_SUFFIX_IDENTIFIER + PROPERTIES_FILE_EXTENSION;
94
95     /**
96      * Installation environment suffix for files.
97      */
98     public static final String ENV_FILE_SUFFIX = ".environment";
99
100     /**
101      * Environment properties extension.
102      */
103     public static final String ENV_FILE_EXTENSION = ENV_FILE_SUFFIX;
104
105     /**
106      * Installation environment suffix for files.
107      */
108     public static final String SYSTEM_PROPERTIES_SUFFIX = "-system";
109     public static final String SYSTEM_PROPERTIES_FILE_SUFFIX = SYSTEM_PROPERTIES_SUFFIX + PROPERTIES_FILE_EXTENSION;
110
111     /**
112      * Logger.
113      */
114     private static final Logger logger = LoggerFactory.getLogger(FileSystemPersistence.class);
115
116     /**
117      * Configuration directory.
118      */
119     protected Path configurationPath = Paths.get(SystemPersistenceConstants.DEFAULT_CONFIGURATION_DIR);
120
121
122     @Override
123     public void setConfigurationDir(String configDir) {
124         String tempConfigDir = configDir;
125
126         if (tempConfigDir == null) {
127             tempConfigDir = SystemPersistenceConstants.DEFAULT_CONFIGURATION_DIR;
128             this.configurationPath = Paths.get(SystemPersistenceConstants.DEFAULT_CONFIGURATION_DIR);
129         }
130
131         if (!tempConfigDir.equals(SystemPersistenceConstants.DEFAULT_CONFIGURATION_DIR)) {
132             this.configurationPath = Paths.get(tempConfigDir);
133         }
134
135         setConfigurationDir();
136     }
137
138     @Override
139     public void setConfigurationDir() {
140         if (Files.notExists(this.configurationPath)) {
141             try {
142                 Files.createDirectories(this.configurationPath);
143             } catch (final IOException e) {
144                 throw new IllegalStateException("cannot create " + this.configurationPath, e);
145             }
146         }
147
148         if (!Files.isDirectory(this.configurationPath)) {
149             throw new IllegalStateException(
150                 "config directory: " + this.configurationPath + " is not a directory");
151         }
152     }
153
154     protected Properties getProperties(Path propertiesPath) {
155         if (!Files.exists(propertiesPath)) {
156             throw new IllegalArgumentException("properties for " + propertiesPath + " are not persisted.");
157         }
158
159         try {
160             return PropertyUtil.getProperties(propertiesPath.toFile());
161         } catch (final Exception e) {
162             throw new IllegalArgumentException("can't read properties for " + propertiesPath, e);
163         }
164     }
165
166     @Override
167     public Properties getProperties(String name) {
168         if (name == null || name.isEmpty()) {
169             throw new IllegalArgumentException("properties name must be provided");
170         }
171
172         var propertiesPath = Paths.get(this.configurationPath.toString());
173         if (name.endsWith(PROPERTIES_FILE_EXTENSION) || name.endsWith(ENV_FILE_EXTENSION)) {
174             propertiesPath = propertiesPath.resolve(name);
175         } else {
176             propertiesPath = propertiesPath.resolve(name + PROPERTIES_FILE_EXTENSION);
177         }
178
179         return getProperties(propertiesPath);
180     }
181
182     protected List<Properties> getPropertiesList(String suffix) {
183         return getPropertiesList(suffix, (name, props) ->  true);
184     }
185
186     protected List<Properties> getPropertiesList(String suffix, BiPredicate<String, Properties> preCondition) {
187         List<Properties> properties = new ArrayList<>();
188         File[] files = this.sortedListFiles();
189         for (File file : files) {
190             if (file.getName().endsWith(suffix)) {
191                 addToPropertiesList(file, properties, preCondition);
192             }
193         }
194         return properties;
195     }
196
197     private void addToPropertiesList(File file, List<Properties> properties,
198                                      BiPredicate<String, Properties> preCondition) {
199         try {
200             var proposedProps = getProperties(file.getName());
201             if (preCondition.test(file.getName(), proposedProps)) {
202                 properties.add(proposedProps);
203             }
204         } catch (final Exception e) {
205             logger.error("{}: cannot get properties {} because of {}", this, file.getName(),
206                 e.getMessage(), e);
207         }
208     }
209
210     @Override
211     public Properties getEnvironmentProperties(String name) {
212         if (name == null || name.isEmpty()) {
213             throw new IllegalArgumentException("environment name must be provided");
214         }
215
216         return this.getProperties(Paths.get(this.configurationPath.toString(), name + ENV_FILE_SUFFIX));
217     }
218
219     @Override
220     public List<Properties> getEnvironmentProperties() {
221         return getPropertiesList(ENV_FILE_SUFFIX);
222     }
223
224     @Override
225     public Properties getSystemProperties(String name) {
226         return this.getProperties(name + SYSTEM_PROPERTIES_SUFFIX);
227     }
228
229     @Override
230     public List<Properties> getSystemProperties() {
231         return getPropertiesList(SYSTEM_PROPERTIES_FILE_SUFFIX);
232     }
233
234     @Override
235     public Properties getEngineProperties() {
236         return this.getProperties(PROPERTIES_FILE_ENGINE);
237     }
238
239     @Override
240     public Properties getControllerProperties(String controllerName) {
241         return this.getProperties(controllerName + CONTROLLER_SUFFIX_IDENTIFIER);
242     }
243
244     @Override
245     public List<Properties> getControllerProperties() {
246         return getPropertiesList(PROPERTIES_FILE_CONTROLLER_SUFFIX, this::testControllerName);
247     }
248
249     @Override
250     public Properties getTopicProperties(String topicName) {
251         return this.getProperties(topicName + TOPIC_SUFFIX_IDENTIFIER);
252     }
253
254     @Override
255     public List<Properties> getTopicProperties() {
256         return getPropertiesList(PROPERTIES_FILE_TOPIC_SUFFIX);
257     }
258
259     @Override
260     public Properties getHttpServerProperties(String serverName) {
261         return this.getProperties(serverName + HTTP_SERVER_SUFFIX_IDENTIFIER);
262     }
263
264     @Override
265     public List<Properties> getHttpServerProperties() {
266         return getPropertiesList(PROPERTIES_FILE_HTTP_SERVER_SUFFIX);
267     }
268
269     @Override
270     public Properties getHttpClientProperties(String clientName) {
271         return this.getProperties(clientName + HTTP_CLIENT_SUFFIX_IDENTIFIER);
272     }
273
274     @Override
275     public List<Properties> getHttpClientProperties() {
276         return getPropertiesList(PROPERTIES_FILE_HTTP_CLIENT_SUFFIX);
277     }
278
279     private boolean testControllerName(String controllerFilename, Properties controllerProperties) {
280         var controllerName = controllerFilename
281                 .substring(0, controllerFilename.length() - PROPERTIES_FILE_CONTROLLER_SUFFIX.length());
282         String controllerPropName = controllerProperties.getProperty(DroolsPropertyConstants.PROPERTY_CONTROLLER_NAME);
283         if (controllerPropName == null) {
284             controllerProperties.setProperty(DroolsPropertyConstants.PROPERTY_CONTROLLER_NAME, controllerName);
285         } else if (!controllerPropName.equals(controllerName)) {
286             logger.error("{}: mismatch controller named {} against {} in file {}",
287                          this, controllerPropName, controllerName, controllerFilename);
288             return false;
289         }
290         return true;
291     }
292
293
294     @Override
295     public boolean backupController(String controllerName) {
296         return backup(controllerName, PROPERTIES_FILE_CONTROLLER_SUFFIX);
297     }
298
299     @Override
300     public boolean backupTopic(String topicName) {
301         return backup(topicName, PROPERTIES_FILE_TOPIC_SUFFIX);
302     }
303
304     @Override
305     public boolean backupHttpServer(String serverName) {
306         return backup(serverName, PROPERTIES_FILE_HTTP_SERVER_SUFFIX);
307     }
308
309     @Override
310     public boolean backupHttpClient(String clientName) {
311         return backup(clientName, PROPERTIES_FILE_HTTP_CLIENT_SUFFIX);
312     }
313
314     protected boolean backup(String name, String fileSuffix) {
315         var path = Paths.get(this.configurationPath.toString(), name + fileSuffix);
316         if (Files.exists(path)) {
317             try {
318                 logger.info("{}: there is an existing configuration file @ {} ", this, path);
319                 var bakPath = Paths.get(this.configurationPath.toString(),
320                                             name + fileSuffix + FILE_BACKUP_SUFFIX);
321                 Files.copy(path, bakPath, StandardCopyOption.REPLACE_EXISTING);
322             } catch (Exception e) {
323                 logger.warn("{}: {} cannot be backed up", this, name, e);
324                 return false;
325             }
326         }
327         return true;
328     }
329
330     @Override
331     public boolean storeController(String controllerName, Object configuration) {
332         checkPropertiesParam(configuration);
333         return store(controllerName, (Properties) configuration, PROPERTIES_FILE_CONTROLLER_SUFFIX);
334     }
335
336     @Override
337     public boolean storeTopic(String topicName, Object configuration) {
338         checkPropertiesParam(configuration);
339         return store(topicName, (Properties) configuration, PROPERTIES_FILE_TOPIC_SUFFIX);
340     }
341
342     @Override
343     public boolean storeHttpServer(String serverName, Object configuration) {
344         checkPropertiesParam(configuration);
345         return store(serverName, (Properties) configuration, PROPERTIES_FILE_HTTP_SERVER_SUFFIX);
346     }
347
348     @Override
349     public boolean storeHttpClient(String clientName, Object configuration) {
350         checkPropertiesParam(configuration);
351         return store(clientName, (Properties) configuration, PROPERTIES_FILE_HTTP_CLIENT_SUFFIX);
352     }
353
354     private boolean store(String name, Properties properties, String fileSuffix) {
355         var path = Paths.get(this.configurationPath.toString(), name + fileSuffix);
356         if (Files.exists(path)) {
357             try {
358                 var oldProperties = PropertyUtil.getProperties(path.toFile());
359                 if (oldProperties.equals(properties)) {
360                     logger.info("{}: noop: a properties file with the same contents exists for controller {}.", this,
361                                 name);
362                     return true;
363                 } else {
364                     this.backupController(name);
365                 }
366             } catch (Exception e) {
367                 logger.info("{}: no existing {} properties", this, name, e);
368                 // continue
369             }
370         }
371
372         var file = path.toFile();
373         try (var writer = new FileWriter(file)) {
374             properties.store(writer, "Machine created Policy Controller Configuration");
375         } catch (Exception e) {
376             logger.warn("{}: {} cannot be saved", this, name, e);
377             return false;
378         }
379
380         return true;
381     }
382
383     private void checkPropertiesParam(Object configuration) {
384         if (!(configuration instanceof Properties)) {
385             throw new IllegalArgumentException(
386                 "configuration must be of type properties to be handled by this manager");
387         }
388     }
389
390
391     @Override
392     public boolean deleteController(String controllerName) {
393         return delete(controllerName, PROPERTIES_FILE_CONTROLLER_SUFFIX);
394     }
395
396     @Override
397     public boolean deleteTopic(String topicName) {
398         return delete(topicName, PROPERTIES_FILE_TOPIC_SUFFIX);
399     }
400
401     @Override
402     public boolean deleteHttpServer(String serverName) {
403         return delete(serverName, PROPERTIES_FILE_HTTP_SERVER_SUFFIX);
404     }
405
406     @Override
407     public boolean deleteHttpClient(String clientName) {
408         return delete(clientName, PROPERTIES_FILE_HTTP_CLIENT_SUFFIX);
409     }
410
411     protected boolean delete(String name, String fileSuffix) {
412         var path = Paths.get(this.configurationPath.toString(), name + fileSuffix);
413
414         if (Files.exists(path)) {
415             try {
416                 var bakPath = Paths.get(this.configurationPath.toString(),
417                                             name + fileSuffix + FILE_BACKUP_SUFFIX);
418                 Files.move(path, bakPath, StandardCopyOption.REPLACE_EXISTING);
419             } catch (final Exception e) {
420                 logger.warn("{}: {} cannot be deleted", this, name, e);
421                 return false;
422             }
423         }
424
425         return true;
426     }
427
428     /**
429      * provides a list of files sorted by name in ascending order in the configuration directory.
430      */
431     private File[] sortedListFiles() {
432         File[] dirFiles = this.configurationPath.toFile().listFiles();
433         if (dirFiles != null) {
434             Arrays.sort(dirFiles, Comparator.comparing(File::getName));
435         } else {
436             dirFiles = new File[]{};
437         }
438         return dirFiles;
439     }
440 }