/*
* 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 static org.osgi.framework.FrameworkUtil.createFilter;
import java.io.Closeable;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import org.jasypt.encryption.StringEncryptor;
import org.ops4j.pax.jdbc.config.impl.tracker.MultiServiceTracker;
import org.ops4j.pax.jdbc.config.impl.tracker.TrackerCallback;
import org.ops4j.pax.jdbc.hook.PreHook;
import org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.jdbc.DataSourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Watches for DataSource configs in OSGi configuration admin and creates / destroys the trackers
* for the DataSourceFactories and pooling support
*/
@SuppressWarnings({ "rawtypes"})
public class DataSourceConfigManager implements ManagedServiceFactory {
private final class TrackerCallbackImpl implements TrackerCallback {
private final Dictionary<String, Object> config;
private TrackerCallbackImpl(Dictionary<String, Object> config) {
this.config = config;
}
@Override
public Closeable activate(MultiServiceTracker tracker) {
StringEncryptor decryptor = tracker.getService(StringEncryptor.class);
Dictionary<String, Object> decryptedConfig = new Decryptor(decryptor).decrypt(config);
PooledDataSourceFactory pdsf = tracker.getService(PooledDataSourceFactory.class);
DataSourceFactory dsf = tracker.getService(DataSourceFactory.class);
DataSourceFactory actualDsf = pdsf != null ? new PoolingWrapper(pdsf, dsf) : dsf;
PreHook preHook = tracker.getService(PreHook.class);
return new DataSourceRegistration(context, actualDsf, config, decryptedConfig, preHook);
}
}
private Logger LOG = LoggerFactory.getLogger(DataSourceConfigManager.class);
BundleContext context;
/**
* Stores one ServiceTracker for DataSourceFactories for each config pid
*/
private Map<String, MultiServiceTracker> trackers;
public DataSourceConfigManager(BundleContext context) {
this.context = context;
this.trackers = new HashMap<String, MultiServiceTracker>();
}
@Override
public String getName() {
return "datasource";
}
@Override
public void updated(final String pid, final Dictionary config) throws ConfigurationException {
deleted(pid);
if (config == null) {
return;
}
try {
Dictionary<String, Object> loadedConfig = new ExternalConfigLoader().resolve(config);
final MultiServiceTracker tracker = createTracker(new TrackerCallbackImpl(loadedConfig));
Filter dsfFilter = getDSFFilter(loadedConfig);
Filter pdsfFilter = getPooledDSFFilter(loadedConfig);
if (Decryptor.isEncrypted(loadedConfig)) {
tracker.track(StringEncryptor.class, getAliasFilter(loadedConfig));
}
if (pdsfFilter != null) {
tracker.track(PooledDataSourceFactory.class, pdsfFilter);
}
tracker.track(DataSourceFactory.class, dsfFilter);
String preHookName = (String)loadedConfig.get(PreHook.CONFIG_KEY_NAME);
if (preHookName != null) {
tracker.track(PreHook.class, createFilter(eqFilter(PreHook.KEY_NAME, preHookName)));
}
tracker.open();
trackers.put(pid, tracker);
}
catch (InvalidSyntaxException e) {
LOG.warn("Invalid filter for DataSource config from pid " + pid, e);
}
}
private Filter getAliasFilter(Dictionary<String, Object> loadedConfig) throws InvalidSyntaxException {
String alias = Decryptor.getAlias(loadedConfig);
return andFilter(eqFilter("objectClassName", StringEncryptor.class.getName()),
eqFilter("alias", alias));
}
/**
* Allows to override tracker creation fort tests
* @param callback
* @return
*/
MultiServiceTracker createTracker(TrackerCallback callback) {
return new MultiServiceTracker(context, callback);
}
private Filter getPooledDSFFilter(Dictionary config)
throws ConfigurationException, InvalidSyntaxException {
String pool = (String)config.remove(PooledDataSourceFactory.POOL_KEY);
boolean isXa = isXa(config);
if (pool == null) {
if (isXa) {
throw new ConfigurationException(null, "Can not create XA DataSource without pooling.");
} else {
return null;
}
}
return andFilter(eqFilter("objectClass", PooledDataSourceFactory.class.getName()),
eqFilter("pool", pool),
eqFilter("xa", Boolean.toString(isXa)));
}
private boolean isXa(Dictionary config) throws ConfigurationException {
String xa = (String) config.remove(PooledDataSourceFactory.XA_KEY);
if (xa == null) {
return false;
} else {
if ("true".equals(xa)) {
return true;
} else if ("false".equals(xa)) {
return false;
} else {
throw new ConfigurationException(null, "Invalid XA configuration provided, XA can only be set to true or false");
}
}
}
private Filter getDSFFilter(Dictionary config) throws ConfigurationException, InvalidSyntaxException {
String driverClass = (String) config.get(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS);
String driverName = (String) config.get(DataSourceFactory.OSGI_JDBC_DRIVER_NAME);
if (driverClass == null && driverName == null) {
throw new ConfigurationException(null,
"Could not determine driver to use. Specify either "
+ DataSourceFactory.OSGI_JDBC_DRIVER_CLASS + " or "
+ DataSourceFactory.OSGI_JDBC_DRIVER_NAME);
}
return andFilter(eqFilter("objectClass", DataSourceFactory.class.getName()),
eqFilter(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS, driverClass),
eqFilter(DataSourceFactory.OSGI_JDBC_DRIVER_NAME, driverName));
}
private String eqFilter(String key, String value) {
return value != null ? "(" + key + "=" + value + ")" : null;
}
private Filter andFilter(String... filterList) throws InvalidSyntaxException {
StringBuilder filter = new StringBuilder();
int count = 0;
for (String filterPart : filterList) {
if (filterPart != null) {
filter.append(filterPart);
count ++;
}
}
if (count > 1) {
filter.append(")");
}
return createFilter(((count > 1) ? "(&" : "") + filter.toString());
}
@Override
public void deleted(String pid) {
MultiServiceTracker tracker = trackers.get(pid);
if (tracker != null) {
tracker.close();
trackers.remove(pid);
}
}
}