/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ops4j.pax.jdbc.config.impl; import java.io.Closeable; import java.sql.SQLException; import java.util.Arrays; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Objects; import java.util.Properties; import java.util.Set; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; import javax.sql.XADataSource; import org.ops4j.pax.jdbc.hook.PreHook; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.jdbc.DataSourceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings({ "rawtypes", "unchecked" }) public class DataSourceRegistration implements Closeable { static final String DATASOURCE_TYPE = "dataSourceType"; static final String JNDI_SERVICE_NAME = "osgi.jndi.service.name"; // By default all local keys (without a dot) are forwarded to the DataSourceFactory. // These config keys will explicitly not be forwarded to the DataSourceFactory // (even though they are "local" keys without a dot ".") // Exception: for pooling support keys with prefix pool or factory are always // forwarded. private static final Set<String> NOT_FORWARDED_KEYS = new HashSet<String>(Arrays.asList(new String []{ DataSourceFactory.JDBC_DATASOURCE_NAME, DATASOURCE_TYPE })); private static final Set<String> FORWARDED_KEY_PREFIXES = new HashSet<>(Arrays.asList(new String[]{ "pool.", "factory." })); // additionally all keys prefixed with "jdbc." will be forwarded (with the prefix stripped). private static final String CONFIG_KEY_PREFIX = "jdbc."; private static Logger LOG = LoggerFactory.getLogger(DataSourceRegistration.class); private AutoCloseable dataSource; private ServiceRegistration serviceReg; public DataSourceRegistration(BundleContext context, DataSourceFactory dsf, final Dictionary config, final Dictionary decryptedConfig, final PreHook preHook) { String dsName = getDSName(config); if (dsName != null) { config.put(JNDI_SERVICE_NAME, dsName); } try { LOG.info("Found DataSourceFactory. Creating DataSource {}", dsName); String typeName = (String)config.get(DATASOURCE_TYPE); Class<?> type = getType(typeName); Object ds = createDs(dsf, type, decryptedConfig); if (ds instanceof AutoCloseable) { dataSource = (AutoCloseable)ds; } if (preHook != null && ds instanceof DataSource) { LOG.info("Executing pre hook for DataSource {}", dsName); preHook.prepare((DataSource)ds); LOG.info("Pre hook finished. Publishing DataSource {}", dsName); } serviceReg = context.registerService(type.getName(), ds, filterHidden(config)); } catch (SQLException e) { LOG.warn(e.getMessage(), e); } } static String getDSName(Dictionary config) { String jndiName = (String)config.get(DataSourceRegistration.JNDI_SERVICE_NAME); String dsName = (String)config.get(DataSourceFactory.JDBC_DATASOURCE_NAME); if (dsName == null && jndiName == null) { throw new IllegalStateException("Can not determine DataSource name. Must set " + DataSourceRegistration.JNDI_SERVICE_NAME + " or " + DataSourceFactory.JDBC_DATASOURCE_NAME); } return jndiName != null ? jndiName : dsName; } @Override public void close() { if (serviceReg != null) { serviceReg.unregister(); } safeClose(dataSource); } private Class<?> getType(String typeName) { if (typeName == null || DataSource.class.getSimpleName().equals(typeName)) { return DataSource.class; } else if (ConnectionPoolDataSource.class.getSimpleName().equals(typeName)) { return ConnectionPoolDataSource.class; } else if (XADataSource.class.getSimpleName().equals(typeName)) { return XADataSource.class; } else { String msg = String.format("Problem in DataSource config : %s must be one of %s , %s, %s", DATASOURCE_TYPE, // DataSource.class.getSimpleName(), // ConnectionPoolDataSource.class.getSimpleName(), // XADataSource.class.getSimpleName()); throw new IllegalArgumentException(msg); } } private Object createDs(DataSourceFactory dsf, Class<?> type, Dictionary decryptedConfig) throws SQLException { Objects.requireNonNull(dsf, "Must provide a DataSourceFactory"); Properties props = toProperties(decryptedConfig); if (type == DataSource.class) { addDataSourceName(dsf, decryptedConfig, props); return dsf.createDataSource(props); } else if (type == ConnectionPoolDataSource.class) { return dsf.createConnectionPoolDataSource(props); } else { return dsf.createXADataSource(props); } } /** * Add dataSourceName for dbcp2 pooled DS to configure JMX bean name * @param dsf * @param config * @param props */ private void addDataSourceName(DataSourceFactory dsf, Dictionary config, Properties props) { Class<? extends DataSourceFactory> dsfClass = dsf.getClass(); if (dsfClass != null && dsfClass.getName().startsWith("org.ops4j.pax.jdbc.pool.dbcp2")) { props.put(DataSourceFactory.JDBC_DATASOURCE_NAME, config.get(DataSourceFactory.JDBC_DATASOURCE_NAME)); } } private Properties toProperties(Dictionary dict) { Properties props = new Properties(); Enumeration keys = dict.keys(); while (keys.hasMoreElements()) { final String originalKey = (String) keys.nextElement(); final String unhiddenKey = unhide(originalKey); if (shouldForwardToDataSourceFactory(unhiddenKey)) { props.put(unhiddenKey, dict.get(originalKey)); } else if (unhiddenKey.startsWith(CONFIG_KEY_PREFIX)) { props.put(unhiddenKey.substring(CONFIG_KEY_PREFIX.length()), dict.get(originalKey)); } } return props; } private boolean shouldForwardToDataSourceFactory(String key) { // only forward local configuration keys (i. e. those without a dot) // exception: the DATASOURCE_TYPE key (as legacy). boolean shouldForward = (!key.contains(".") && !NOT_FORWARDED_KEYS.contains(key)); for (Iterator<String> it = FORWARDED_KEY_PREFIXES.iterator(); !shouldForward && it.hasNext(); ) { shouldForward = key.startsWith(it.next()); } return shouldForward; } private Dictionary filterHidden(Dictionary dict) { final Dictionary filtered = new Hashtable(dict.size()); final Enumeration keys = dict.keys(); while (keys.hasMoreElements()) { final String key = (String)keys.nextElement(); if (!isHidden(key)) { filtered.put(key, dict.get(key)); } } return filtered; } private String unhide(String key) { return isHidden(key) ? key.substring(1) : key; } private boolean isHidden(String key) { return key != null && key.startsWith("."); } private void safeClose(AutoCloseable closeable) { if (closeable != null) { try { closeable.close(); } catch (Exception e) { LOG.warn("Error closing " + closeable.getClass() + ": " + e.getMessage(), e); } } } }