001 /*
002 * Copyright 2008-2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package griffon.core.env;
017
018 import java.io.*;
019 import java.lang.ref.Reference;
020 import java.lang.ref.SoftReference;
021 import java.net.URL;
022 import java.util.*;
023 import java.util.regex.Pattern;
024
025 /**
026 * Represents the application Metadata and loading mechanics
027 *
028 * @author Graeme Rocher (Grails 1.1)
029 */
030
031 public class Metadata extends Properties {
032
033 public static final String FILE = "application.properties";
034 public static final String APPLICATION_VERSION = "application.version";
035 public static final String APPLICATION_NAME = "application.name";
036
037 private static final Pattern SKIP_PATTERN = Pattern.compile("^.*/griffon-.*.jar!/application.properties$");
038 private static Reference<Metadata> metadata = new SoftReference<>(new Metadata());
039
040 private boolean initialized;
041 private File metadataFile;
042 private boolean dirty;
043
044 private Metadata() {
045 super();
046 }
047
048 private Metadata(File f) {
049 this.metadataFile = f;
050 }
051
052 /**
053 * Resets the current state of the Metadata so it is re-read
054 */
055 public static void reset() {
056 Metadata m = metadata.get();
057 if (m != null) {
058 m.clear();
059 m.initialized = false;
060 m.dirty = false;
061 }
062 }
063
064 /**
065 * @return Returns the metadata for the current application
066 */
067 public static Metadata getCurrent() {
068 Metadata m = metadata.get();
069 if (m == null) {
070 metadata = new SoftReference<>(new Metadata());
071 m = metadata.get();
072 }
073 if (!m.initialized) {
074 InputStream input = null;
075 try {
076 // GRIFFON-108 enable reading metadata from a local file IF AND ONLY IF
077 // current environment == 'dev'.
078 // must read environment directly from System.properties to avoid a
079 // circular problem
080 // if(Environment.getEnvironment(System.getProperty(Environment.KEY)) == Environment.DEVELOPMENT) {
081 // input = new FileInputStream(FILE);
082 // }
083
084 // GRIFFON-255 there may be multiple versions of "application.properties" in the classpath
085 // due to addon packaging. Avoid any URLS that look like plugin dirs or addon jars
086 input = fetchApplicationProperties(Metadata.class.getClassLoader());
087
088 if (input != null) {
089 m.load(input);
090 }
091 } catch (Exception e) {
092 throw new RuntimeException("Cannot load application metadata:" + e.getMessage(), e);
093 } finally {
094 closeQuietly(input);
095 m.initialized = true;
096 }
097 }
098
099 return m;
100 }
101
102 /**
103 * Loads a Metadata instance from a Reader
104 *
105 * @param inputStream The InputStream
106 * @return a Metadata instance
107 */
108 public static Metadata getInstance(InputStream inputStream) {
109 Metadata m = new Metadata();
110 metadata = new FinalReference<>(m);
111
112 try {
113 m.load(inputStream);
114 m.initialized = true;
115 } catch (IOException e) {
116 throw new RuntimeException("Cannot load application metadata:" + e.getMessage(), e);
117 }
118 return m;
119 }
120
121 /**
122 * Loads and returns a new Metadata object for the given File
123 *
124 * @param file The File
125 * @return A Metadata object
126 */
127 public static Metadata getInstance(File file) {
128 Metadata m = new Metadata(file);
129 metadata = new FinalReference<>(m);
130
131 if (file != null && file.exists()) {
132 FileInputStream input = null;
133 try {
134 input = new FileInputStream(file);
135 m.load(input);
136 m.initialized = true;
137 } catch (Exception e) {
138 throw new RuntimeException("Cannot load application metadata:" + e.getMessage(), e);
139 } finally {
140 closeQuietly(input);
141 }
142 }
143 return m;
144 }
145
146 /**
147 * Reloads the application metadata
148 *
149 * @return The metadata object
150 */
151 public static Metadata reload() {
152 File f = getCurrent().metadataFile;
153
154 if (f != null) {
155 return getInstance(f);
156 }
157 return getCurrent();
158 }
159
160 /**
161 * @return The application version
162 */
163 public String getApplicationVersion() {
164 return (String) get(APPLICATION_VERSION);
165 }
166
167 /**
168 * @return The environment the application expects to run in
169 */
170 public String getEnvironment() {
171 return (String) get(Environment.KEY);
172 }
173
174 /**
175 * @return The application name
176 */
177 public String getApplicationName() {
178 return (String) get(APPLICATION_NAME);
179 }
180
181 /**
182 * Saves the current state of the Metadata object
183 */
184 public void persist() {
185 if (dirty && metadataFile != null) {
186 if (!isFileDirty(metadataFile)) {
187 dirty = false;
188 return;
189 }
190
191 FileOutputStream out = null;
192
193 try {
194 out = new FileOutputStream(metadataFile);
195 store(out, "Griffon Metadata file");
196 dirty = false;
197 } catch (Exception e) {
198 throw new RuntimeException("Error persisting metadata to file [" + metadataFile + "]: " + e.getMessage(), e);
199 } finally {
200 closeQuietly(out);
201 }
202 }
203 }
204
205 private boolean isFileDirty(File file) {
206 InputStream in = null;
207
208 try {
209 Properties other = new Properties();
210 in = new FileInputStream(metadataFile);
211 other.load(in);
212
213 for (Map.Entry<Object, Object> entry : other.entrySet()) {
214 if (!containsKey(entry.getKey()) || !get(entry.getKey()).equals(entry.getValue())) {
215 return true;
216 }
217 }
218
219 for (Map.Entry<Object, Object> entry : entrySet()) {
220 if (!other.containsKey(entry.getKey()) || !other.get(entry.getKey()).equals(entry.getValue())) {
221 return true;
222 }
223 }
224 } catch (Exception e) {
225 throw new RuntimeException("Error reading metadata to file [" + file + "]: " + e.getMessage(), e);
226 } finally {
227 closeQuietly(in);
228 }
229 return false;
230 }
231
232 /**
233 * @return Returns true if these properties have not changed since they were loaded
234 */
235 public boolean propertiesHaveNotChanged() {
236 return dirty;
237 }
238
239 @Override
240 public synchronized Object remove(Object o) {
241 boolean hasKey = containsKey(o);
242 Object value = super.remove(o);
243 dirty = dirty || hasKey;
244 return value;
245 }
246
247 @Override
248 public synchronized Object setProperty(String name, String value) {
249 return put(name, value);
250 }
251
252 @Override
253 public synchronized Object put(Object key, Object value) {
254 if (key instanceof CharSequence) key = key.toString();
255 if (value instanceof CharSequence) value = value.toString();
256 if (containsKey(key)) {
257 Object oldValue = get(key);
258 if (oldValue instanceof CharSequence) {
259 oldValue = oldValue.toString();
260 }
261 dirty = dirty || (oldValue != null && !oldValue.equals(value));
262 } else {
263 dirty = true;
264 }
265 return super.put(key, value);
266 }
267
268 @Override
269 public synchronized void putAll(Map<?, ?> map) {
270 for (Map.Entry<?, ?> entry : map.entrySet()) {
271 put(entry.getKey(), entry.getValue());
272 }
273 }
274
275 /**
276 * Overrides, called by the store method.
277 */
278 @SuppressWarnings("unchecked")
279 public synchronized Enumeration keys() {
280 Enumeration keysEnum = super.keys();
281 Vector keyList = new Vector<>();
282 while (keysEnum.hasMoreElements()) {
283 keyList.add(keysEnum.nextElement());
284 }
285 Collections.sort(keyList);
286 return keyList.elements();
287 }
288
289 private static void closeQuietly(Closeable c) {
290 if (c != null) {
291 try {
292 c.close();
293 } catch (Exception ignored) {
294 // ignored
295 }
296 }
297 }
298
299 static class FinalReference<T> extends SoftReference<T> {
300 private T ref;
301
302 public FinalReference(T t) {
303 super(t);
304 this.ref = t;
305 }
306
307 @Override
308 public T get() {
309 return ref;
310 }
311 }
312
313 private static InputStream fetchApplicationProperties(ClassLoader classLoader) {
314 Enumeration<URL> urls;
315
316 try {
317 urls = classLoader.getResources(FILE);
318 } catch (IOException ioe) {
319 throw new RuntimeException(ioe);
320 }
321
322 while (urls.hasMoreElements()) {
323 try {
324 URL url = urls.nextElement();
325 if (SKIP_PATTERN.matcher(url.toString()).matches()) continue;
326 return url.openStream();
327 } catch (IOException ioe) {
328 // skip
329 }
330 }
331
332 return null;
333 }
334 }
|