Metadata.java
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 (Stringget(APPLICATION_VERSION);
165     }
166 
167     /**
168      @return The environment the application expects to run in
169      */
170     public String getEnvironment() {
171         return (Stringget(Environment.KEY);
172     }
173 
174     /**
175      @return The application name
176      */
177     public String getApplicationName() {
178         return (Stringget(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 CharSequencekey = key.toString();
255         if (value instanceof CharSequencevalue = 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 }