View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common.config;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.PrintWriter;
25  import java.io.StringWriter;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Optional;
29  import java.util.regex.Pattern;
30  import java.util.stream.Collectors;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.mycore.common.MCRException;
35  import org.mycore.common.MCRPropertiesResolver;
36  
37  public final class MCRConfigurationBase {
38      static final Pattern PROPERTY_SPLITTER = Pattern.compile(",");
39  
40      /**
41       * The properties instance that stores the values that have been read from every configuration file. These
42       * properties are unresolved
43       */
44      private static MCRProperties baseProperties = new MCRProperties();
45  
46      /**
47       * The same as baseProperties but all %properties% are resolved.
48       */
49      private static MCRProperties resolvedProperties = new MCRProperties();
50  
51      /**
52       * List of deprecated properties with their new name
53       */
54      private static MCRProperties deprecatedProperties = new MCRProperties();
55  
56      private static File lastModifiedFile;
57  
58      static {
59          try {
60              createLastModifiedFile();
61          } catch (IOException e) {
62              throw new MCRConfigurationException("Could not initialize MyCoRe configuration", e);
63          }
64      }
65  
66      private MCRConfigurationBase() {
67      }
68  
69      /**
70       * returns the last point in time when the MyCoRe system was last modified. This method can help you to validate
71       * caches not under your controll, e.g. client caches.
72       *
73       * @see System#currentTimeMillis()
74       */
75      public static long getSystemLastModified() {
76          return lastModifiedFile.lastModified();
77      }
78  
79      /**
80       * signalize that the system state has changed. Call this method when ever you changed the persistency layer.
81       */
82      public static void systemModified() {
83          if (!lastModifiedFile.exists()) {
84              try {
85                  createLastModifiedFile();
86              } catch (IOException ioException) {
87                  throw new MCRException("Could not change modify date of file " + lastModifiedFile.getAbsolutePath(),
88                      ioException);
89              }
90          } else if (!lastModifiedFile.setLastModified(System.currentTimeMillis())) {
91              // a problem occurs, when a linux user other than the file owner
92              // tries to change the last modified date
93              // @see Java Bug:
94              // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4466073
95              // fixable in Java7 with setTimes() method of new file system API
96              // workaround for now: try to recreate the file
97              // @author Robert Stephan
98              FileOutputStream fout = null;
99              try {
100                 try {
101                     fout = new FileOutputStream(lastModifiedFile);
102                     fout.write(new byte[0]);
103                     lastModifiedFile.setWritable(true, false);
104                 } finally {
105                     if (fout != null) {
106                         fout.close();
107                     }
108                 }
109             } catch (IOException e) {
110                 throw new MCRException("Could not change modify date of file " + lastModifiedFile.getAbsolutePath(), e);
111             }
112         }
113     }
114 
115     /**
116      * Creates a new .systemTime file in MCR.datadir.
117      */
118     private static synchronized void createLastModifiedFile() throws IOException {
119         final String dataDirKey = "MCR.datadir";
120         if (getResolvedProperties().containsKey(dataDirKey)) {
121             Optional<File> dataDir = getString(dataDirKey)
122                 .map(File::new);
123             if (dataDir.filter(File::exists)
124                 .filter(File::isDirectory).isPresent()) {
125                 lastModifiedFile = dataDir.map(p -> new File(p, ".systemTime")).get();
126             } else {
127                 dataDir
128                     .ifPresent(d -> System.err.println("WARNING: MCR.dataDir does not exist: " + d.getAbsolutePath()));
129             }
130         }
131         if (lastModifiedFile == null) {
132             try {
133                 lastModifiedFile = File.createTempFile("MyCoRe", ".systemTime");
134                 lastModifiedFile.deleteOnExit();
135             } catch (IOException e) {
136                 throw new MCRException("Could not create temporary file, please set property MCR.datadir");
137             }
138         }
139         if (!lastModifiedFile.exists()) {
140             FileOutputStream fout = null;
141             try {
142                 fout = new FileOutputStream(lastModifiedFile);
143                 fout.write(new byte[0]);
144             } finally {
145                 if (fout != null) {
146                     fout.close();
147                 }
148             }
149             //allow other users to change this file
150             lastModifiedFile.setWritable(true, false);
151         }
152     }
153 
154     private static void debug() {
155         String comments = "Active mycore properties";
156         File resolvedPropertiesFile = MCRConfigurationDir.getConfigFile("mycore.resolved.properties");
157         if (resolvedPropertiesFile != null) {
158             try (FileOutputStream fout = new FileOutputStream(resolvedPropertiesFile)) {
159                 getResolvedProperties().store(fout, comments + "\nDo NOT edit this file!");
160             } catch (IOException e) {
161                 LogManager.getLogger()
162                     .warn("Could not store resolved properties to {}", resolvedPropertiesFile.getAbsolutePath(),
163                         e);
164             }
165         }
166 
167         Logger logger = LogManager.getLogger();
168         if (logger.isDebugEnabled()) {
169             try (StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw)) {
170                 getResolvedProperties().store(out, comments);
171                 out.flush();
172                 sw.flush();
173                 logger.debug(sw.toString());
174             } catch (IOException e) {
175                 logger.debug("Error while debugging mycore properties.", e);
176             }
177         }
178     }
179 
180     /**
181      * Substitute all %properties%.
182      */
183     protected static synchronized void resolveProperties() {
184         MCRProperties tmpProperties = MCRProperties.copy(getBaseProperties());
185         MCRPropertiesResolver resolver = new MCRPropertiesResolver(tmpProperties);
186         resolvedProperties = MCRProperties.copy(resolver.resolveAll(tmpProperties));
187     }
188 
189     private static void checkForDeprecatedProperties(Map<String, String> props) {
190         Map<String, String> depUsedProps = props.entrySet().stream()
191             .filter(e -> getDeprecatedProperties().containsKey(e.getKey()))
192             .collect(Collectors.toMap(Map.Entry::getKey, e -> getDeprecatedProperties().getAsMap().get(e.getKey())));
193         if (!depUsedProps.isEmpty()) {
194             throw new MCRConfigurationException(
195                 depUsedProps.entrySet().stream().map(e -> e.getKey() + " ==> " + e.getValue())
196                     .collect(Collectors.joining("\n",
197                         "Found deprecated properties that are defined but will NOT BE USED. "
198                             + "Please use the replacements:\n",
199                         "\n")));
200         }
201     }
202 
203     private static void checkForDeprecatedProperty(String name) throws MCRConfigurationException {
204         if (getDeprecatedProperties().containsKey(name)) {
205             throw new MCRConfigurationException("Cannot set deprecated property " + name + ". Please use "
206                 + getDeprecatedProperties().getProperty(name) + " instead.");
207         }
208     }
209 
210     protected static MCRProperties getResolvedProperties() {
211         return resolvedProperties;
212     }
213 
214     protected static MCRProperties getBaseProperties() {
215         return baseProperties;
216     }
217 
218     protected static MCRProperties getDeprecatedProperties() {
219         return deprecatedProperties;
220     }
221 
222     /**
223      * Returns the configuration property with the specified name as a String.
224      *
225      * @param name
226      *            the non-null and non-empty name of the configuration property
227      * @return the value of the configuration property as a String
228      * @throws MCRConfigurationException
229      *             if the properties are not initialized
230      */
231     public static Optional<String> getString(String name) {
232         if (Objects.requireNonNull(name, "MyCoRe property name must not be null.").trim().isEmpty()) {
233             throw new MCRConfigurationException("MyCoRe property name must not be empty.");
234         }
235         if (name.trim() != name) {
236             throw new MCRConfigurationException(
237                 "MyCoRe property name must not contain trailing or leading whitespaces: '" + name + "'");
238         }
239         if (getBaseProperties().isEmpty()) {
240             throw new MCRConfigurationException("MCRConfiguration is still not initialized");
241         }
242         return getStringUnchecked(name);
243     }
244 
245     static Optional<String> getStringUnchecked(String name) {
246         checkForDeprecatedProperty(name);
247         return Optional.ofNullable(getResolvedProperties().getProperty(name, null));
248     }
249 
250     /**
251      * Sets the configuration property with the specified name to a new <CODE>
252      * String</CODE> value. If the parameter <CODE>value</CODE> is <CODE>
253      * null</CODE>, the property will be deleted.
254      *
255      * @param name
256      *            the non-null and non-empty name of the configuration property
257      * @param value
258      *            the new value of the configuration property, possibly <CODE>
259      *            null</CODE>
260      */
261     static void set(String name, String value) {
262         checkForDeprecatedProperty(name);
263         if (value == null) {
264             getBaseProperties().remove(name);
265         } else {
266             getBaseProperties().setProperty(name, value);
267         }
268         resolveProperties();
269     }
270 
271     public static synchronized void initialize(Map<String, String> deprecated, Map<String, String> props,
272         boolean clear) {
273         if (clear) {
274             deprecatedProperties.clear();
275         }
276         deprecatedProperties.putAll(deprecated);
277         checkForDeprecatedProperties(props);
278         if (clear) {
279             getBaseProperties().clear();
280         } else {
281             getBaseProperties().entrySet()
282                 .removeIf(e -> props.containsKey(e.getKey()) && props.get(e.getKey()) == null);
283         }
284         getBaseProperties().putAll(
285             props.entrySet()
286                 .stream()
287                 .filter(e -> e.getKey() != null)
288                 .filter(e -> e.getValue() != null)
289                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
290         resolveProperties();
291         debug();
292     }
293 
294 }