001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2001-2005 Julian Hyde 008// Copyright (C) 2005-2011 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.olap; 012 013import org.apache.log4j.Logger; 014 015import org.eigenbase.util.property.TriggerableProperties; 016 017import java.io.*; 018import java.net.*; 019import java.util.Enumeration; 020 021/** 022 * <code>MondrianProperties</code> contains the properties which determine the 023 * behavior of a mondrian instance. 024 * 025 * <p>There is a method for property valid in a 026 * <code>mondrian.properties</code> file. Although it is possible to retrieve 027 * properties using the inherited {@link java.util.Properties#getProperty(String)} 028 * method, we recommend that you use methods in this class. 029 * 030 * <h2>Note to developers</h2> 031 * 032 * If you add a property, you must:<ul> 033 * 034 * <li>Add a property definition to MondrianProperties.xml.</li> 035 * 036 * <li>Re-generate MondrianProperties.java using PropertyUtil.</li> 037 * 038 * <li>Modify the default <code>mondrian.properties</code> file checked into 039 * source control, with a description of the property and its default 040 * value.</li> 041 * 042 * <li>Modify the 043 * <a target="_top" href="{@docRoot}/../configuration.html#Property_list"> 044 * Configuration Specification</a>.</li> 045 * </ul> 046 * 047 * <p>Similarly if you update or delete a property. 048 * 049 * @author jhyde 050 * @since 22 December, 2002 051 */ 052public abstract class MondrianPropertiesBase extends TriggerableProperties { 053 054 private final PropertySource propertySource; 055 private int populateCount; 056 057 private static final Logger LOGGER = 058 Logger.getLogger(MondrianProperties.class); 059 060 protected static final String mondrianDotProperties = "mondrian.properties"; 061 062 protected MondrianPropertiesBase(PropertySource propertySource) { 063 this.propertySource = propertySource; 064 } 065 066 public boolean triggersAreEnabled() { 067 return ((MondrianProperties) this).EnableTriggers.get(); 068 } 069 070 /** 071 * Represents a place that properties can be read from, and remembers the 072 * timestamp that we last read them. 073 */ 074 public interface PropertySource { 075 /** 076 * Opens an input stream from the source. 077 * 078 * <p>Also checks the 'last modified' time, which will determine whether 079 * {@link #isStale()} returns true. 080 * 081 * @return input stream 082 */ 083 InputStream openStream(); 084 085 /** 086 * Returns true if the source exists and has been modified since last 087 * time we called {@link #openStream()}. 088 * 089 * @return whether source has changed since it was last read 090 */ 091 boolean isStale(); 092 093 /** 094 * Returns the description of this source, such as a filename or URL. 095 * 096 * @return description of this PropertySource 097 */ 098 String getDescription(); 099 } 100 101 /** 102 * Implementation of {@link PropertySource} which reads from a 103 * {@link java.io.File}. 104 */ 105 static class FilePropertySource implements PropertySource { 106 private final File file; 107 private long lastModified; 108 109 FilePropertySource(File file) { 110 this.file = file; 111 this.lastModified = 0; 112 } 113 114 public InputStream openStream() { 115 try { 116 this.lastModified = file.lastModified(); 117 return new FileInputStream(file); 118 } catch (FileNotFoundException e) { 119 throw Util.newInternal( 120 e, 121 "Error while opening properties file '" + file + "'"); 122 } 123 } 124 125 public boolean isStale() { 126 return file.exists() 127 && file.lastModified() > this.lastModified; 128 } 129 130 public String getDescription() { 131 return "file=" + file.getAbsolutePath() 132 + " (exists=" + file.exists() + ")"; 133 } 134 } 135 136 /** 137 * Implementation of {@link PropertySource} which reads from a 138 * {@link java.net.URL}. 139 */ 140 static class UrlPropertySource implements PropertySource { 141 private final URL url; 142 private long lastModified; 143 144 UrlPropertySource(URL url) { 145 this.url = url; 146 } 147 148 private URLConnection getConnection() { 149 try { 150 return url.openConnection(); 151 } catch (IOException e) { 152 throw Util.newInternal( 153 e, 154 "Error while opening properties file '" + url + "'"); 155 } 156 } 157 158 public InputStream openStream() { 159 try { 160 final URLConnection connection = getConnection(); 161 this.lastModified = connection.getLastModified(); 162 return connection.getInputStream(); 163 } catch (IOException e) { 164 throw Util.newInternal( 165 e, 166 "Error while opening properties file '" + url + "'"); 167 } 168 } 169 170 public boolean isStale() { 171 final long lastModified = getConnection().getLastModified(); 172 return lastModified > this.lastModified; 173 } 174 175 public String getDescription() { 176 return url.toExternalForm(); 177 } 178 } 179 180 /** 181 * Loads this property set from: the file "$PWD/mondrian.properties" (if it 182 * exists); the "mondrian.properties" in the CLASSPATH; and from the system 183 * properties. 184 */ 185 public void populate() { 186 // Read properties file "mondrian.properties", if it exists. If we have 187 // read the file before, only read it if it is newer. 188 loadIfStale(propertySource); 189 190 URL url = null; 191 File file = new File(mondrianDotProperties); 192 if (file.exists() && file.isFile()) { 193 // Read properties file "mondrian.properties" from PWD, if it 194 // exists. 195 try { 196 url = file.toURI().toURL(); 197 } catch (MalformedURLException e) { 198 LOGGER.warn( 199 "Mondrian: file '" 200 + file.getAbsolutePath() 201 + "' could not be loaded", e); 202 } 203 } else { 204 // Then try load it from classloader 205 url = 206 MondrianPropertiesBase.class.getClassLoader().getResource( 207 mondrianDotProperties); 208 } 209 210 if (url != null) { 211 load(new UrlPropertySource(url)); 212 } else { 213 LOGGER.warn( 214 "mondrian.properties can't be found under '" 215 + new File(".").getAbsolutePath() + "' or classloader"); 216 } 217 218 // copy in all system properties which start with "mondrian." 219 int count = 0; 220 for (Enumeration<?> keys = System.getProperties().keys(); 221 keys.hasMoreElements();) 222 { 223 String key = (String) keys.nextElement(); 224 String value = System.getProperty(key); 225 if (key.startsWith("mondrian.")) { 226 // NOTE: the super allows us to bybase calling triggers 227 // Is this the correct behavior? 228 if (LOGGER.isDebugEnabled()) { 229 LOGGER.debug("populate: key=" + key + ", value=" + value); 230 } 231 super.setProperty(key, value); 232 count++; 233 } 234 } 235 if (populateCount++ == 0) { 236 LOGGER.info( 237 "Mondrian: loaded " + count + " system properties"); 238 } 239 } 240 241 /** 242 * Reads properties from a source. 243 * If the source does not exist, or has not changed since we last read it, 244 * does nothing. 245 * 246 * @param source Source of properties 247 */ 248 private void loadIfStale(PropertySource source) { 249 if (source.isStale()) { 250 if (LOGGER.isDebugEnabled()) { 251 LOGGER.debug("Mondrian: loading " + source.getDescription()); 252 } 253 load(source); 254 } 255 } 256 257 /** 258 * Tries to load properties from a URL. Does not fail, just prints success 259 * or failure to log. 260 * 261 * @param source Source to read properties from 262 */ 263 private void load(final PropertySource source) { 264 try { 265 load(source.openStream()); 266 if (populateCount == 0) { 267 LOGGER.info( 268 "Mondrian: properties loaded from '" 269 + source.getDescription() 270 + "'"); 271 } 272 } catch (IOException e) { 273 LOGGER.error( 274 "Mondrian: error while loading properties " 275 + "from '" + source.getDescription() + "' (" + e + ")"); 276 } 277 } 278} 279 280// End MondrianPropertiesBase.java