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.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.net.URL;
28  import java.nio.charset.StandardCharsets;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.LinkedList;
33  import java.util.List;
34  import java.util.Properties;
35  
36  import org.apache.commons.io.IOUtils;
37  import org.apache.logging.log4j.LogManager;
38  import org.mycore.common.content.MCRContent;
39  import org.mycore.common.content.MCRFileContent;
40  import org.mycore.common.content.MCRURLContent;
41  
42  /**
43   * A InputStream from (preferably) property files. All available InputStreams are combined in this order:
44   * <ol>
45   * <li>mycore-base</li>
46   * <li>other mycore-components</li>
47   * <li>application modules</li>
48   * <li>installation specific files</li>
49   * </ol>
50   * 
51   * @author Thomas Scheffler (yagee)
52   * @author Robert Stephan
53   * @since 2013.12
54   */
55  public class MCRConfigurationInputStream extends InputStream {
56  
57      private static final String MYCORE_PROPERTIES = "mycore.properties";
58  
59      // latin1 for properties
60      private static final byte[] LINE_BREAK = System.getProperty("line.separator").getBytes(StandardCharsets.ISO_8859_1);
61  
62      InputStream in;
63  
64      private Enumeration<? extends InputStream> e;
65  
66      private boolean empty;
67  
68      /**
69       * Combined Stream of all config files named <code>filename</code> available via
70       * {@link MCRRuntimeComponentDetector#getAllComponents()}.
71       * 
72       * @param filename
73       *            , e.g. mycore.properties or messages_de.properties
74       */
75      public MCRConfigurationInputStream(String filename) throws IOException {
76          this(filename, null);
77      }
78  
79      private MCRConfigurationInputStream(String filename, InputStream initStream) throws IOException {
80          super();
81          this.empty = true;
82          this.e = getPropertyInputStreams(filename, initStream);
83          if (e.hasMoreElements()) {
84              nextStream();
85          }
86      }
87  
88      /**
89       * {@link InputStream} that includes all properties from {@link MCRRuntimeComponentDetector#getAllComponents()} and
90       * <strong>mycore.properties</strong>. Use system property <code>MCR.Configuration.File</code> to configure
91       * alternative property file.
92       * 
93       * @since 2014.04
94       */
95      public static MCRConfigurationInputStream getMyCoRePropertiesInstance() throws IOException {
96          File configurationDirectory = MCRConfigurationDir.getConfigurationDirectory();
97          InputStream initStream = null;
98          if (configurationDirectory != null) {
99              LogManager.getLogger().info("Current configuration directory: {}",
100                 configurationDirectory.getAbsolutePath());
101             // set MCR.basedir, is normally overwritten later
102             if (configurationDirectory.isDirectory()) {
103                 initStream = getBaseDirInputStream(configurationDirectory);
104             }
105         }
106         return new MCRConfigurationInputStream(MYCORE_PROPERTIES, initStream);
107     }
108 
109     public boolean isEmpty() {
110         return empty;
111     }
112 
113     private Enumeration<? extends InputStream> getPropertyInputStreams(String filename, InputStream initStream)
114         throws IOException {
115         LinkedList<InputStream> cList = new LinkedList<>();
116         if (initStream != null) {
117             empty = false;
118             cList.add(initStream);
119         }
120         for (MCRComponent component : MCRRuntimeComponentDetector.getAllComponents()) {
121             InputStream is = component.getConfigFileStream(filename);
122             if (is != null) {
123                 empty = false;
124                 String comment = "\n\n#\n#\n# Component: " + component.getName() + "\n#\n#\n";
125                 cList.add(new ByteArrayInputStream(comment.getBytes(StandardCharsets.ISO_8859_1)));
126                 cList.add(is);
127                 //workaround if last property is not terminated with line break
128                 cList.add(new ByteArrayInputStream(LINE_BREAK));
129             } else {
130                 cList.add(new ByteArrayInputStream(
131                     ("# Unable to find " + filename + " in " + component.getResourceBase() + "\n")
132                         .getBytes(StandardCharsets.ISO_8859_1)));
133             }
134         }
135         InputStream propertyStream = getConfigFileStream(filename);
136         if (propertyStream != null) {
137             empty = false;
138             cList.add(propertyStream);
139             cList.add(new ByteArrayInputStream(LINE_BREAK));
140         }
141         File localProperties = MCRConfigurationDir.getConfigFile(filename);
142         if (localProperties != null && localProperties.canRead()) {
143             empty = false;
144             LogManager.getLogger().info("Loading additional properties from {}", localProperties.getAbsolutePath());
145             cList.add(new FileInputStream(localProperties));
146             cList.add(new ByteArrayInputStream(LINE_BREAK));
147         }
148         return Collections.enumeration(cList);
149     }
150 
151     private static ByteArrayInputStream getBaseDirInputStream(File configurationDirectory) throws IOException {
152         Properties dataProp = new Properties();
153         //On Windows we require forward slashes
154         dataProp.setProperty("MCR.basedir", configurationDirectory.getAbsolutePath().replace('\\', '/'));
155         ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
156         dataProp.store(out, null);
157         return new ByteArrayInputStream(out.toByteArray());
158     }
159 
160     private static InputStream getConfigFileStream(String filename) throws IOException {
161         File cfgFile = new File(filename);
162         MCRContent input = null;
163         if (cfgFile.canRead()) {
164             input = new MCRFileContent(cfgFile);
165         } else {
166             URL url = MCRConfigurationInputStream.class.getClassLoader().getResource(filename);
167             if (url != null) {
168                 input = new MCRURLContent(url);
169             }
170         }
171         return input == null ? null : input.getInputStream();
172     }
173 
174     /**
175      * return an enumeration of input streams of configuration files
176      * found in MyCoRe components and modules, respecting the proper loading order 
177      */
178     public static List<byte[]> getConfigFileContents(String filename) throws IOException {
179         ArrayList<byte[]> cList = new ArrayList<>();
180         for (MCRComponent component : MCRRuntimeComponentDetector.getAllComponents()) {
181             try (InputStream is = component.getConfigFileStream(filename)) {
182                 if (is != null) {
183                     cList.add(IOUtils.toByteArray(is));
184                 }
185             }
186         }
187         // load config file from classpath
188         try (InputStream configStream = getConfigFileStream(filename)) {
189             if (configStream != null) {
190                 LogManager.getLogger().debug("Loaded config file from classpath: " + filename);
191                 cList.add(IOUtils.toByteArray(configStream));
192             }
193         }
194 
195         //load config file from app config dir
196         File localConfigFile = MCRConfigurationDir.getConfigFile(filename);
197         if (localConfigFile != null && localConfigFile.canRead()) {
198             LogManager.getLogger().debug("Loaded config file from config dir: " + filename);
199             try (FileInputStream fis = new FileInputStream(localConfigFile)) {
200                 cList.add(IOUtils.toByteArray(fis));
201             }
202         }
203         return cList;
204     }
205 
206     /**
207      * Continues reading in the next stream if an EOF is reached.
208      */
209     final void nextStream() throws IOException {
210         if (in != null) {
211             in.close();
212         }
213 
214         if (e.hasMoreElements()) {
215             in = e.nextElement();
216             if (in == null) {
217                 throw new NullPointerException();
218             }
219         } else {
220             in = null;
221         }
222 
223     }
224 
225     @Override
226     public int available() throws IOException {
227         if (in == null) {
228             return 0; // no way to signal EOF from available()
229         }
230         return in.available();
231     }
232 
233     @Override
234     public int read() throws IOException {
235         if (in == null) {
236             return -1;
237         }
238         int c = in.read();
239         if (c == -1) {
240             nextStream();
241             return read();
242         }
243         return c;
244     }
245 
246     @Override
247     public int read(byte[] b, int off, int len) throws IOException {
248         if (in == null) {
249             return -1;
250         }
251 
252         int n = in.read(b, off, len);
253         if (n <= 0) {
254             nextStream();
255             return read(b, off, len);
256         }
257         return n;
258     }
259 
260     @Override
261     public void close() throws IOException {
262         do {
263             nextStream();
264         } while (in != null);
265     }
266 }