001/*
002 * JPPF.
003 * Copyright (C) 2005-2018 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 */
018
019package org.jppf.persistence;
020
021import java.util.*;
022import java.util.regex.*;
023
024import javax.sql.DataSource;
025
026import org.jppf.management.JPPFSystemInformation;
027import org.jppf.node.policy.*;
028import org.jppf.utils.*;
029import org.jppf.utils.configuration.ConfigurationUtils;
030import org.slf4j.*;
031
032/**
033 * This class represents a registry and factory for data sources defined via JPPF configuration properties.
034 * @author Laurent Cohen
035 */
036public final class JPPFDatasourceFactory {
037  /**
038   * Logger for this class.
039   */
040  private static Logger log = LoggerFactory.getLogger(JPPFDatasourceFactory.class);
041  /**
042   * Determines whether the debug level is enabled in the log configuration, without the cost of a method call.
043   */
044  private static boolean debugEnabled = log.isDebugEnabled();
045  /**
046   * Start prefix for the name of all properties that define datasources.
047   */
048  private static final String DS_PROP_PREFIX = "jppf.datasource.";
049  /**
050   * Regex pattern for a property that assings a datasource name.
051   */
052  private static final Pattern DS_NAME_PATTERN = Pattern.compile("^" + DS_PROP_PREFIX.replace(".", "\\.") + "(.*)\\.name$");
053  //private static final Pattern DS_NAME_PATTERN = Pattern.compile("^jppf\\.datasource\\.(.*)\\.name$");
054  /**
055   * Singleton instance of this class.
056   */
057  private static JPPFDatasourceFactory INSTANCE;
058  /**
059   * Mapping of datasources to their assigned name.
060   */
061  private final Map<String, DataSource> dsMap = new HashMap<>();
062  /**
063   * Performs the actual creation of the datasources and connection pools.
064   */
065  private final DatasourceInitializer initializer;
066  /**
067   * Possible scopes.
068   * @exclude
069   */
070  public enum Scope {
071    /**
072     * Local JVM only.
073     */
074    LOCAL,
075    /**
076     * Remote nodes only.
077     */
078    REMOTE,
079    /**
080     * Local JVM and remote nodes.
081     */
082    ANY
083  }
084
085  /**
086   * Can't be instantiated from another class.
087   * @param initializer the initializer to use.
088   */
089  private JPPFDatasourceFactory(final DatasourceInitializer initializer) {
090    this.initializer = initializer;
091  }
092
093  /**
094   * Get a data source factory. This method always returns the same instance.
095   * @return a {@link JPPFDatasourceFactory} instance.
096   */
097  public synchronized static JPPFDatasourceFactory getInstance() {
098    if (INSTANCE == null) INSTANCE = new JPPFDatasourceFactory(new DatasourceInitializerImpl());
099    return INSTANCE;
100  }
101
102  /**
103   * Get the data source with the specified name.
104   * @param name the name assigned to the data source.
105   * @return a DataSource instance, or {@code null} if no such data source could be retrieved.
106   */
107  public DataSource getDataSource(final String name) {
108    synchronized(dsMap) {
109      return dsMap.get(name);
110    }
111  }
112
113  /**
114   * Get the names of all currently defined data sources.
115   * @return a {@link List} of datasource names.
116   */
117  public List<String> getDataSourceNames() {
118    synchronized(dsMap) {
119      return new ArrayList<>(dsMap.keySet());
120    }
121  }
122
123  /**
124   * Create a data source from the specified configuration properties. This method assumes the property names have no prefix.
125   * @param name the name of the data source to create.
126   * @param props the data source configuration properties.
127   * @return a {@link DataSource} instance.
128   */
129  public DataSource createDataSource(final String name, final Properties props) {
130    if (name != null) props.setProperty("name", name);
131    final Pair<String, DataSource> p = createDataSourceInternal(props, null, null);
132    return (p == null) ? null : p.second();
133  }
134
135  /**
136   * Create one or more data sources from the specified configuration properties. This method assumes the property names have
137   * the same format as for static datasources configurations, that is:<br>
138   * {@code jppf.datasource.<configId>.<property_name> = <property_value>}.
139   * @param props the data sources configuration properties.
140   * @return a {@link Map} whose keys are the names of the created data sources and whose values are the corresponding {@link DataSource} objects.
141   */
142  public Map<String, DataSource> createDataSources(final Properties props) {
143    final TypedProperties config = (props instanceof TypedProperties) ? (TypedProperties) props : new TypedProperties(props);
144    return configure(extractDefinitions(config, Scope.LOCAL), null);
145  }
146
147  /**
148   * Remove the data source with the specified name. This method also closes the data source and releases the resources it is using.
149   * @param name the name assigned to the datasource.
150   * @return {@code true} if the removal was succcessful, {@code false} if it fails for any reason, including if no data source with the specified name exists.
151   */
152  public boolean removeDataSource(final String name) {
153    synchronized(dsMap) {
154      final DataSource ds = dsMap.remove(name);
155      if (ds != null) initializer.close(ds);
156      return ds != null;
157    }
158  }
159
160  /**
161   * Close and remove all the data sources in this registry.
162   */
163  public void clear() {
164    synchronized(dsMap) {
165      for (DataSource ds: dsMap.values()) initializer.close(ds);
166      dsMap.clear();
167    }
168  }
169
170  /**
171   * Create and configure the datasources defined in the specified configuration.
172   * @param config the configuration properties that define the datasources.
173   * @param scope determines which scope to create data sources for.
174   * @exclude
175   */
176  public void configure(final TypedProperties config, final Scope scope) {
177    configure(extractDefinitions(config, scope), null);
178  }
179
180  /**
181   * Create and configure the datasources defined in the specified configuration.
182   * @param config the configuration properties that define the datasources.
183   * @param scope determines which scope to create datasources for.
184   * @param info the system information match an eventual execution policy.
185   * @exclude
186   */
187  public void configure(final TypedProperties config, final Scope scope, final JPPFSystemInformation info) {
188    configure(extractDefinitions(config, scope), info);
189  }
190
191  /**
192   * Create and configure the datasources defined in the specified configurations.
193   * @param definitionsMap a mapping of config ids and corresponding map of properties.
194   * @exclude
195   */
196  public void configure(final Map<String, TypedProperties> definitionsMap) {
197    configure(definitionsMap, null);
198  }
199
200  /**
201   * Create and configure the datasources defined in the specified configurations.
202   * The definitions may contain an execution policy which is then matched against
203   * the specified {@link JPPFSystemInformation} to determine whteher the datasource should b created.
204   * @param definitionsMap a mapping of config ids and corresponding map of properties.
205   * @param info the system information match an eventual execution policy.
206   * @return a {@link Map} whose keys are the names of the created datasources and whose values are the corresponding {@link DataSource} objects.
207   * @exclude
208   */
209  public Map<String, DataSource> configure(final Map<String, TypedProperties> definitionsMap, final JPPFSystemInformation info) {
210    final Map<String, DataSource> result = new HashMap<>();
211    for (final Map.Entry<String, TypedProperties> entry: definitionsMap.entrySet()) {
212      final Pair<String, DataSource> p = createDataSourceInternal(entry.getValue(), entry.getKey(), info);
213      if (p != null) result.put(p.first(), p.second());
214    }
215    return result;
216  }
217
218  /**
219   * Create and configure the datasources defined in the specified configuration.
220   * @param config the configuration properties that define the datasources.
221   * @param requestedScope in a driver, determines whether the definitions are intended for the nodes ({@code true}) or for the local JVM ({@code false}).
222   * @return a mapping of config ids to the corresponding properties.
223   * @exclude
224   */
225  public static Map<String, TypedProperties> extractDefinitions(final TypedProperties config, final Scope requestedScope) {
226    final Scope reqScope = (requestedScope == null) ? Scope.LOCAL : requestedScope;
227    final TypedProperties allDSProps = config.filter(new TypedProperties.Filter() {
228      @Override
229      public boolean accepts(final String name, final String value) {
230        return (name != null) && name.startsWith(DS_PROP_PREFIX);
231      }
232    });
233    final List<String> ids = getConfigIds(allDSProps, reqScope);
234    if (debugEnabled) log.debug("datasource configuration ids with scope={}: {}", reqScope, ids);
235    final Map<String, TypedProperties> result = new HashMap<>(ids.size());
236    for (final String id: ids) {
237      final String prefix = DS_PROP_PREFIX + id + ".";
238      final TypedProperties dsProps = allDSProps.filter(new TypedProperties.Filter() {
239        @Override
240        public boolean accepts(final String name, final String value) {
241          return (name != null) && name.startsWith(prefix);
242        }
243      });
244      resolvePolicy(dsProps, id);
245      result.put(id, dsProps);
246    }
247    return result;
248  }
249
250  /**
251   * 
252   * @param allDSProps the configuration properties that define the datasources.
253   * @param reqScope in a driver, determines whether the definitions are intended for the nodes ({@code true}) or for the local JVM ({@code false}).
254   * @return a list of datasource configuration ids, possibly empty but never null.
255   */
256  private static List<String> getConfigIds(final TypedProperties allDSProps, final Scope reqScope) {
257    final List<String> ids = new ArrayList<>();
258    for (final String name: allDSProps.stringPropertyNames()) {
259      final Matcher matcher = DS_NAME_PATTERN.matcher(name);
260      if (matcher.matches()) {
261        final String id = matcher.group(1);
262        final String s = allDSProps.getString(DS_PROP_PREFIX + id + ".scope", Scope.LOCAL.name());
263        Scope actualScope = Scope.LOCAL;
264        for (final Scope sc: Scope.values()) {
265          if (sc.name().equalsIgnoreCase(s)) {
266            actualScope = sc;
267            break;
268          }
269        }
270        if ((actualScope == Scope.ANY) || (actualScope == reqScope)) ids.add(id);
271      }
272    }
273    return ids;
274  }
275
276  /**
277   * Create a datasource from the specified configuration properties. The {@code configId} is used to build a prefix for
278   * the relevant property names, in the format {@code jppf.datasource.<configId>.<property_name> = <value>}.
279   * If configId is {@code null}, then no prefix is applied.
280   * @param props the datasource properties.
281   * @param configId the identifier of the datasource in the configuration.
282   * @param info the system information match an eventual execution policy.
283   * @return a {@link Pair} made of the datasource name and its corresponding {@link DataSource} instance.
284   */
285  private Pair<String, DataSource> createDataSourceInternal(final Properties props, final String configId, final JPPFSystemInformation info) {
286    final String prefix = (configId == null) ? null : DS_PROP_PREFIX + configId + ".";
287    final String start = (prefix == null) ? "" : prefix;
288    final String dsName = props.getProperty(start + "name");
289    if (dsName == null) return null;
290    if (getDataSource(dsName) != null) {
291      log.warn("ignoring duplicate definition for datasource '{}' : {}", dsName, ConfigurationUtils.hidePasswords(props));
292      return null;
293    }
294    if (info != null) {
295      final String policyDef = props.getProperty(start + "policy");
296      final String policyText = props.getProperty(start + "policy.text");
297      if ((policyDef != null) && (policyText != null)) {
298        try {
299          final ExecutionPolicy policy = PolicyParser.parsePolicy(policyText);
300          if (policy != null) {
301            if (!policy.evaluate(info)) {
302              if (debugEnabled) log.debug("datasource '{}' execution policy does not macth for this node, it will be ignored", dsName);
303              return null;
304            }
305          }
306        } catch (final Exception e) {
307          log.error("failed to parse execution policy for datasource '{}'\npolicy definition: {}\npolicy text: {}\nException: {}",
308            dsName, policyDef, policyText, ExceptionUtils.getStackTrace(e));
309        }
310      }
311    }
312    final TypedProperties cleanProps = new TypedProperties();
313    for (final String key: props.stringPropertyNames()) {
314      final String newKey = (prefix != null) && key.startsWith(prefix) ? key.substring(prefix.length()) : key;
315      cleanProps.put(newKey, props.getProperty(key));
316    }
317    synchronized(dsMap) {
318      final DataSource ds = initializer.createDataSource(cleanProps, dsName);
319      dsMap.put(dsName, ds);
320      if (debugEnabled) log.debug("defined datasource with configId={}, name={}, properties={}", configId, dsName, ConfigurationUtils.hidePasswords(cleanProps));
321      return new Pair<>(dsName, ds);
322    }
323  }
324
325  /**
326   * Create the execution policy form the defintiion.
327   * @param props the datasource properties.
328   * @param configId the identifier of the datasource in the configuration.
329   * @return an {@link ExecutionPolicy} instance, or {@code null} if none could be created.
330   */
331  private static  String resolvePolicy(final TypedProperties props, final String configId) {
332    final String prefix = (configId == null) ? "" : "jppf.datasource." + configId + ".";
333    final String policy = PolicyUtils.resolvePolicy(props, prefix + "policy");
334    if (policy != null) props.setString(prefix + "policy.text", policy);
335    return policy;
336  }
337}