001 /*
002 * Copyright 2004-2012 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.util;
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 public static final String FILE = "application.properties";
033 public static final String APPLICATION_VERSION = "app.version";
034 public static final String APPLICATION_NAME = "app.name";
035 public static final String APPLICATION_GRIFFON_VERSION = "app.griffon.version";
036 public static final String GRIFFON_START_DIR = "griffon.start.dir";
037 public static final String GRIFFON_WORKING_DIR = "griffon.working.dir";
038 public static final String APPLICATION_TOOLKIT = "app.toolkit";
039
040 private static final Pattern SKIP_PATTERN = Pattern.compile("^.*\\/griffon-.*.jar!\\/application.properties$");
041 private static Reference<Metadata> metadata = new SoftReference<Metadata>(new Metadata());
042
043 private boolean initialized;
044 private File metadataFile;
045
046 private Metadata() {
047 super();
048 }
049
050 private Metadata(File f) {
051 this.metadataFile = f;
052 }
053
054 /**
055 * Resets the current state of the Metadata so it is re-read
056 */
057 public static void reset() {
058 Metadata m = metadata.get();
059 if (m != null) {
060 m.clear();
061 m.initialized = false;
062 }
063 }
064
065 /**
066 * @return Returns the metadata for the current application
067 */
068 public static Metadata getCurrent() {
069 Metadata m = metadata.get();
070 if (m == null) {
071 metadata = new SoftReference<Metadata>(new Metadata());
072 m = metadata.get();
073 }
074 if (!m.initialized) {
075 InputStream input = null;
076 try {
077 // GRIFFON-108 enable reading metadata from a local file IF AND ONLY IF
078 // current environment == 'dev'.
079 // must read environment directly from System.properties to avoid a
080 // circular problem
081 // if(Environment.getEnvironment(System.getProperty(Environment.KEY)) == Environment.DEVELOPMENT) {
082 // input = new FileInputStream(FILE);
083 // }
084
085 // GRIFFON-255 there may be multiple versions of "application.properties" in the classpath
086 // due to addon packaging. Avoid any URLS that look like plugin dirs or addon jars
087 input = fetchApplicationProperties(Thread.currentThread().getContextClassLoader());
088 if (input == null) input = fetchApplicationProperties(Metadata.class.getClassLoader());
089
090 if (input != null) {
091 m.load(input);
092 }
093 } catch (Exception e) {
094 throw new RuntimeException("Cannot load application metadata:" + e.getMessage(), e);
095 } finally {
096 closeQuietly(input);
097 m.initialized = true;
098 }
099 }
100
101 return m;
102 }
103
104 /**
105 * Loads a Metadata instance from a Reader
106 *
107 * @param inputStream The InputStream
108 * @return a Metadata instance
109 */
110 public static Metadata getInstance(InputStream inputStream) {
111 Metadata m = new Metadata();
112 metadata = new FinalReference<Metadata>(m);
113
114 try {
115 m.load(inputStream);
116 m.initialized = true;
117 } catch (IOException e) {
118 throw new RuntimeException("Cannot load application metadata:" + e.getMessage(), e);
119 }
120 return m;
121 }
122
123 /**
124 * Loads and returns a new Metadata object for the given File
125 *
126 * @param file The File
127 * @return A Metadata object
128 */
129 public static Metadata getInstance(File file) {
130 Metadata m = new Metadata(file);
131 metadata = new FinalReference<Metadata>(m);
132
133 if (file != null && file.exists()) {
134 FileInputStream input = null;
135 try {
136 input = new FileInputStream(file);
137 m.load(input);
138 m.initialized = true;
139 } catch (Exception e) {
140 throw new RuntimeException("Cannot load application metadata:" + e.getMessage(), e);
141 } finally {
142 closeQuietly(input);
143 }
144 }
145 return m;
146 }
147
148 /**
149 * Reloads the application metadata
150 *
151 * @return The metadata object
152 */
153 public static Metadata reload() {
154 File f = getCurrent().metadataFile;
155
156 if (f != null) {
157 return getInstance(f);
158 }
159 return getCurrent();
160 }
161
162 /**
163 * @return The application version
164 */
165 public String getApplicationVersion() {
166 return (String) get(APPLICATION_VERSION);
167 }
168
169 /**
170 * @return The Griffon version used to build the application
171 */
172 public String getGriffonVersion() {
173 return (String) get(APPLICATION_GRIFFON_VERSION);
174 }
175
176 /**
177 * @return The environment the application expects to run in
178 */
179 public String getEnvironment() {
180 return (String) get(Environment.KEY);
181 }
182
183 /**
184 * @return The application name
185 */
186 public String getApplicationName() {
187 return (String) get(APPLICATION_NAME);
188 }
189
190 /**
191 * @return Supported toolkit by this application
192 */
193 public String getApplicationToolkit() {
194 return (String) get(APPLICATION_TOOLKIT);
195 }
196
197 /**
198 * Obtains a map (name->version) of installed plugins specified in the project metadata
199 *
200 * @return A map of installed plugins
201 */
202 public Map<String, String> getInstalledPlugins() {
203 Map<String, String> newMap = new LinkedHashMap<String, String>();
204
205 for (Map.Entry<Object, Object> entry : entrySet()) {
206 String key = entry.getKey().toString();
207 Object val = entry.getValue();
208 if (key.startsWith("plugins.") && val != null) {
209 newMap.put(key.substring(8), val.toString());
210 }
211 }
212 return newMap;
213 }
214
215 public Map<String, String> getArchetype() {
216 Map<String, String> newMap = new LinkedHashMap<String, String>();
217
218 for (Map.Entry<Object, Object> entry : entrySet()) {
219 String key = entry.getKey().toString();
220 Object val = entry.getValue();
221 if (key.startsWith("archetype.") && val != null) {
222 newMap.put("name", key.substring(10));
223 newMap.put("version", val.toString());
224 break;
225 }
226 }
227 return newMap;
228 }
229
230 /**
231 * Returns the application's starting directory.<p>
232 * The value comes from the System property 'griffon.start.dir'
233 * if set. Result may be null.
234 *
235 * @return The application start directory path
236 */
237 public String getGriffonStartDir() {
238 String griffonStartDir = (String) get(GRIFFON_START_DIR);
239 if (griffonStartDir == null) {
240 griffonStartDir = System.getProperty(GRIFFON_START_DIR);
241 if (griffonStartDir != null && griffonStartDir.length() > 1 &&
242 griffonStartDir.startsWith("\"") && griffonStartDir.endsWith("\"")) {
243 // normalize without double quotes
244 griffonStartDir = griffonStartDir.substring(1, griffonStartDir.length() - 1);
245 System.setProperty(GRIFFON_START_DIR, griffonStartDir);
246 }
247 if (griffonStartDir != null && griffonStartDir.length() > 1 &&
248 griffonStartDir.startsWith("'") && griffonStartDir.endsWith("'")) {
249 // normalize without single quotes
250 griffonStartDir = griffonStartDir.substring(1, griffonStartDir.length() - 1);
251 System.setProperty(GRIFFON_START_DIR, griffonStartDir);
252 }
253 if (griffonStartDir != null) {
254 put(GRIFFON_START_DIR, griffonStartDir);
255 }
256 }
257 return griffonStartDir;
258 }
259
260 /**
261 * Returns ia non-null value for the application's starting directory.<p>
262 * the path to new File(".") if that path is writable, returns
263 * the value of 'user.dir' otherwise.
264 *
265 * @return The application start directory path
266 */
267 public String getGriffonStartDirSafe() {
268 String griffonStartDir = getGriffonStartDir();
269 if (griffonStartDir == null) {
270 File path = new File(".");
271 if (path.canWrite()) {
272 return path.getAbsolutePath();
273 }
274 return System.getProperty("user.dir");
275 }
276 return griffonStartDir;
277 }
278
279 /**
280 * @return The application working directory
281 */
282 public File getGriffonWorkingDir() {
283 String griffonWorkingDir = (String) get(GRIFFON_WORKING_DIR);
284 if (griffonWorkingDir == null) {
285 String griffonStartDir = getGriffonStartDirSafe();
286 File workDir = new File(griffonStartDir);
287 if (workDir.canWrite()) {
288 put(GRIFFON_WORKING_DIR, griffonStartDir);
289 return workDir;
290 } else {
291 try {
292 File temp = File.createTempFile("griffon", ".tmp");
293 temp.deleteOnExit();
294 workDir = new File(temp.getParent(), getApplicationName());
295 put(GRIFFON_WORKING_DIR, workDir.getAbsolutePath());
296 return workDir;
297 } catch (IOException ioe) {
298 // ignore ??
299 // should not happen
300 }
301 }
302 }
303
304 return new File(griffonWorkingDir);
305 }
306
307 /**
308 * Saves the current state of the Metadata object
309 */
310 public void persist() {
311 // if (propertiesHaveNotChanged()) return;
312
313 if (metadataFile != null) {
314 FileOutputStream out = null;
315
316 try {
317 out = new FileOutputStream(metadataFile);
318 store(out, "Griffon Metadata file");
319 } catch (Exception e) {
320 throw new RuntimeException("Error persisting metadata to file [" + metadataFile + "]: " + e.getMessage(), e);
321 } finally {
322 closeQuietly(out);
323 }
324 }
325 }
326
327 /**
328 * @return Returns true if these properties have not changed since they were loaded
329 */
330 public boolean propertiesHaveNotChanged() {
331 Metadata transientMetadata = getCurrent();
332
333 Metadata allStringValuesMetadata = new Metadata();
334 Map<Object, Object> transientMap = (Map<Object, Object>) transientMetadata;
335 for (Map.Entry<Object, Object> entry : transientMap.entrySet()) {
336 if (entry.getValue() != null) {
337 allStringValuesMetadata.put(entry.getKey().toString(), entry.getValue().toString());
338 }
339 }
340
341 Metadata persistedMetadata = Metadata.reload();
342 boolean result = allStringValuesMetadata.equals(persistedMetadata);
343 metadata = new SoftReference<Metadata>(transientMetadata);
344 return result;
345 }
346
347 /**
348 * Overrides, called by the store method.
349 */
350 @SuppressWarnings("unchecked")
351 public synchronized Enumeration keys() {
352 Enumeration keysEnum = super.keys();
353 Vector keyList = new Vector();
354 while (keysEnum.hasMoreElements()) {
355 keyList.add(keysEnum.nextElement());
356 }
357 Collections.sort(keyList);
358 return keyList.elements();
359 }
360
361 private static void closeQuietly(Closeable c) {
362 if (c != null) {
363 try {
364 c.close();
365 } catch (Exception ignored) {
366 // ignored
367 }
368 }
369 }
370
371 static class FinalReference<T> extends SoftReference<T> {
372 private T ref;
373
374 public FinalReference(T t) {
375 super(t);
376 this.ref = t;
377 }
378
379 @Override
380 public T get() {
381 return ref;
382 }
383 }
384
385 private static InputStream fetchApplicationProperties(ClassLoader classLoader) {
386 Enumeration<URL> urls = null;
387
388 try {
389 urls = classLoader.getResources(FILE);
390 } catch (IOException ioe) {
391 throw new RuntimeException(ioe);
392 }
393
394 while (urls.hasMoreElements()) {
395 try {
396 URL url = urls.nextElement();
397 if (SKIP_PATTERN.matcher(url.toString()).matches()) continue;
398 return url.openStream();
399 } catch (IOException ioe) {
400 // skip
401 }
402 }
403
404 return null;
405 }
406 }
|