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