griffon/util/Metadata.java

`Metadata.java`
`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 }`