001/*
002 * JPPF.
003 * Copyright (C) 2005-2016 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jppf.utils;
019
020import java.io.*;
021
022import org.jppf.serialization.JPPFSerialization;
023import org.jppf.ssl.SSLHelper;
024import org.jppf.utils.configuration.*;
025import org.slf4j.*;
026
027/**
028 * Utility class for loading and accessing the JPPF configuration properties.
029 * <p>The configuration file path is set through the system property {@link org.jppf.utils#CONFIG_PROPERTY CONFIG_PROPERTY},
030 * whose value is &quot;jppf.config&quot;.<br>
031 * As an example, it can be configured by adding the JVM argument &quot;<i>-Djppf.config=jppf-config.properties</i>&quot;.
032 * <p>Modified to allow users to get configuration properties from an alternate source. Any user-provided class that
033 * implements {@link ConfigurationSource} and returns a stream with the same configuration values provided in the properties file.
034 * <br>The configuration source must then configured via the system property <i>-Djppf.config.plugin = mypackage.MyConfigurationSource</i>&quot;
035 * @author Laurent Cohen
036 * @author Jonathan Newbrough
037 */
038public final class JPPFConfiguration {
039  /**
040   * Logger for this class.
041   */
042  private static Logger log = LoggerFactory.getLogger(JPPFConfiguration.class);
043  /**
044   * Name of the system property holding the location of the JPPF configuration file.
045   */
046  public static final String CONFIG_PROPERTY = "jppf.config";
047  /**
048   * The name of the system property used to specified an alternate JPPF configuration source.
049   */
050  public static final String CONFIG_PLUGIN_PROPERTY = "jppf.config.plugin";
051  /**
052   * Default location of the JPPF configuration file.
053   */
054  public static final String DEFAULT_FILE = "jppf.properties";
055  /**
056   * Holds the JPPF configuration properties.
057   */
058  private static TypedProperties props = null;
059
060  /**
061   * Prevent instantiation from another class.
062   */
063  private JPPFConfiguration() {
064  }
065
066  /**
067   * Get the configuration properties.
068   * @return a TypedProperties instance.
069   */
070  public static TypedProperties getProperties() {
071    if (props == null) loadProperties();
072    return props;
073  }
074
075  /**
076   * Get the value of a predefined property.
077   * This is the same as calling {@code JPPFConfiguration.getProperties().get(property)}.
078   * @param <T> the type of the property.
079   * @param property the property whose value to retrieve.
080   * @return the value of the property according to its type.
081   */
082  public static <T> T get(final JPPFProperty<T> property) {
083    return getProperties().get(property);
084  }
085
086  /**
087   * Set the value of a predefined property.
088   * This is the same as calling {@code JPPFConfiguration.getProperties().set(property, value)}.
089   * @param <T> the type of the property.
090   * @param property the property whose value to set.
091   * @param value the value to set.
092   * @return the {@link TypedProperties} instance in which the value is set.
093   */
094  public static <T> TypedProperties set(final JPPFProperty<T> property, final T value) {
095    return getProperties().set(property, value);
096  }
097
098  /**
099   * Remove the predefined property.
100   * This is the same as calling {@code JPPFConfiguration.getProperties().remove(property)}.
101   * @param <T> the type of the property.
102   * @param property the property whose value to retrieve.
103   * @return the value of the property according to its type.
104   */
105  public static <T> T remove(final JPPFProperty<T> property) {
106    return getProperties().remove(property);
107  }
108
109  /**
110   * Reset and reload the JPPF configuration.
111   * This allows reloading the configuration from a different source or file
112   * (after changing the values of the related system properties for instance).
113   */
114  public static void reset() {
115    SSLHelper.resetConfig();
116    loadProperties();
117    JPPFSerialization.Factory.reset();
118  }
119
120  /**
121   * Load the JPPF configuration properties from a file.
122   */
123  private static void loadProperties() {
124    props = new TypedProperties();
125    try (Reader reader = getReader()) {
126      if (reader != null) props.loadAndResolve(reader);
127    } catch(Exception e) {
128      log.error("error reading the configuration", e);
129    }
130    if (log.isTraceEnabled()) {
131      StringBuilder sb = new StringBuilder("predefined configuration properties:");
132      for (JPPFProperty<?> prop: JPPFProperties.allProperties()) sb.append('\n').append(prop.toString());
133      log.trace(sb.toString());
134    }
135  }
136
137  /**
138   * Get an input stream from which to read the configuration properties.
139   * @return an {@link InputStream} instance.
140   * @throws Exception if any error occurs while trying to obtain the stream.
141   */
142  private static Reader getReader() throws Exception {
143    String altSource = System.getProperty(CONFIG_PLUGIN_PROPERTY);
144    if ((altSource != null) && "".equals(altSource.trim())) altSource = null;
145    String filename = System.getProperty(CONFIG_PROPERTY, DEFAULT_FILE);
146    return getConfigurationReader(filename, altSource);
147  }
148
149  /**
150   * Get an inputStream for a properties file located either by the specified filename or configuration source.
151   * @param filename specifies the loaction of the properties file in the file system or classpath.
152   * @param configurationSourceName fully qualified name of a class implementating {@link JPPFConfiguration.ConfigurationSource}.
153   * @return an input stream that can be used to load the properties.
154   * @throws Exception if any error occurs while trying to obtain the stream.
155   */
156  private static Reader getConfigurationReader(final String filename, final String configurationSourceName) throws Exception {
157    Reader reader = null;
158    if (configurationSourceName != null) {
159      reader = getConfigurationSourceReader(configurationSourceName);
160    } else {
161      if (log.isDebugEnabled()) log.debug("reading JPPF configuration file: " + filename);
162      reader = FileUtils.getFileReader(filename);
163    }
164    return reader;
165  }
166
167  /**
168   * Get an inputStream for a properties file located by the specified configuration source.
169   * @param configurationSourceName fully qualified name of a class implementing {@link JPPFConfiguration.ConfigurationSource}
170   * or {@link JPPFConfiguration.ConfigurationSourceReader}.
171   * @return an input stream that can be used to load the properties.
172   * @throws Exception if any error occurs while trying to obtain the stream.
173   * @exclude
174   */
175  public static Reader getConfigurationSourceReader(final String configurationSourceName) throws Exception {
176    Reader reader = null;
177    if (log.isDebugEnabled()) log.debug("reading JPPF configuration from config source: " + configurationSourceName);
178    Class<?> clazz = Class.forName(configurationSourceName);
179    if (ConfigurationSourceReader.class.isAssignableFrom(clazz)) {
180      ConfigurationSourceReader source = (ConfigurationSourceReader) clazz.newInstance();
181      reader = source.getPropertyReader();
182    } else if (ConfigurationSource.class.isAssignableFrom(clazz)) {
183      ConfigurationSource source = (ConfigurationSource) clazz.newInstance();
184      InputStream is = source.getPropertyStream();
185      reader = new InputStreamReader(is);
186    }
187    else throw new IllegalArgumentException("the type '" + configurationSourceName + "' is neither a JPPFConfiguration.ConfigurationSource " + 
188      "nor a JPPFConfiguration.ConfigurationSourceReader");
189    return reader;
190  }
191
192  /**
193   * Implement this interface to provide an alternate configuration source via an {@link InputStream}.
194   * <p>WARNING: not shown in the interface but also required: implementations must have a public no-arg constructor.
195   */
196  public interface ConfigurationSource {
197    /**
198     * Obtain the JPPF configuration properties from an input stream.
199     * The returned stream content must conform to the properties file's specifications
200     * (i.e. it must be usable as the argument to <code>Properties.load(InputStream)</code>).
201     * @return an {@link InputStream} instance.
202     * @throws IOException if the stream cannot be created.
203     */
204    InputStream getPropertyStream() throws IOException;
205  }
206
207  /**
208   * Implement this interface to provide an alternate configuration source via a {@link Reader}.
209   * <p>WARNING: not shown in the interface but also required: implementations must have a public no-arg constructor.
210   */
211  public interface ConfigurationSourceReader {
212    /**
213     * Obtain the JPPF configuration properties from a {@link Reader}.
214     * The returned reader content must conform to the properties file's specifications
215     * (i.e. it must be usable as the argument to <code>Properties.load(InputStream)</code>).
216     * @return an {@link Reader} instance.
217     * @throws IOException if the stream cannot be created.
218     */
219    Reader getPropertyReader() throws IOException;
220  }
221}