| /* |
| * 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.apache.openjpa.slice.jdbc; |
| |
| import java.sql.Connection; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.concurrent.ExecutorService; |
| |
| import javax.sql.DataSource; |
| import javax.sql.XADataSource; |
| |
| import org.apache.openjpa.conf.OpenJPAConfiguration; |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; |
| import org.apache.openjpa.jdbc.schema.DataSourceFactory; |
| import org.apache.openjpa.lib.conf.BooleanValue; |
| import org.apache.openjpa.lib.conf.ConfigurationProvider; |
| import org.apache.openjpa.lib.conf.PluginValue; |
| import org.apache.openjpa.lib.conf.StringListValue; |
| import org.apache.openjpa.lib.conf.StringValue; |
| import org.apache.openjpa.lib.jdbc.DecoratingDataSource; |
| import org.apache.openjpa.lib.jdbc.DelegatingDataSource; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.log.LogFactory; |
| import org.apache.openjpa.lib.log.LogFactoryImpl; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.slice.DistributedBrokerImpl; |
| import org.apache.openjpa.slice.DistributionPolicy; |
| import org.apache.openjpa.slice.ExecutorServiceValue; |
| import org.apache.openjpa.slice.ProductDerivation; |
| import org.apache.openjpa.slice.Slice; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Implements a distributed configuration of JDBCStoreManagers. |
| * The original configuration properties are analyzed to create a set of |
| * Slice specific properties with defaulting rules. |
| * |
| * @author Pinaki Poddar |
| * |
| */ |
| public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl |
| implements DistributedJDBCConfiguration { |
| |
| private final List<Slice> _slices = new ArrayList<Slice>(); |
| private List<String> _activeSliceNames = new ArrayList<String>(); |
| private Slice _master; |
| |
| private DecoratingDataSource virtualDataSource; |
| |
| protected BooleanValue lenientPlugin; |
| protected StringValue masterPlugin; |
| protected StringListValue namesPlugin; |
| protected ExecutorServiceValue executorServicePlugin; |
| protected PluginValue distributionPolicyPlugin; |
| |
| public static final String DOT = "."; |
| public static final String REGEX_DOT = "\\."; |
| public static final String PREFIX_SLICE = ProductDerivation.PREFIX_SLICE + DOT; |
| public static final String PREFIX_OPENJPA = "openjpa."; |
| private static Localizer _loc = |
| Localizer.forPackage(DistributedJDBCConfigurationImpl.class); |
| |
| /** |
| * Configure itself as well as underlying slices. |
| * |
| */ |
| public DistributedJDBCConfigurationImpl(ConfigurationProvider cp) { |
| super(true, false); |
| Map p = cp.getProperties(); |
| String pUnit = getPersistenceUnitName(p); |
| setDiagnosticContext(pUnit); |
| |
| brokerPlugin.setString(DistributedBrokerImpl.class.getName()); |
| |
| distributionPolicyPlugin = addPlugin("DistributionPolicy", true); |
| distributionPolicyPlugin.setDynamic(true); |
| |
| lenientPlugin = addBoolean("Lenient"); |
| |
| masterPlugin = addString("Master"); |
| |
| namesPlugin = addStringList("Names"); |
| |
| executorServicePlugin = new ExecutorServiceValue(); |
| addValue(executorServicePlugin); |
| |
| setSlices(p); |
| } |
| |
| private String getPersistenceUnitName(Map p) { |
| Object unit = p.get(PREFIX_OPENJPA+id.getProperty()); |
| return (unit == null) ? "?" : unit.toString(); |
| } |
| |
| private void setDiagnosticContext(String unit) { |
| LogFactory logFactory = getLogFactory(); |
| if (logFactory instanceof LogFactoryImpl) { |
| ((LogFactoryImpl)logFactory).setDiagnosticContext(unit); |
| } |
| } |
| |
| /** |
| * Gets the name of the active slices. |
| */ |
| public List<String> getActiveSliceNames() { |
| if (_activeSliceNames.isEmpty()) { |
| for (Slice slice:_slices) |
| if (slice.isActive()) |
| _activeSliceNames.add(slice.getName()); |
| } |
| return _activeSliceNames; |
| } |
| |
| /** |
| * Gets the name of the available slices. |
| */ |
| public List<String> getAvailableSliceNames() { |
| List<String> result = new ArrayList<String>(); |
| for (Slice slice:_slices) |
| result.add(slice.getName()); |
| return result; |
| } |
| |
| /** |
| * Gets the slices of given status. Null returns all irrespective of status. |
| */ |
| public List<Slice> getSlices(Slice.Status...statuses) { |
| if (statuses == null) |
| return Collections.unmodifiableList(_slices); |
| List<Slice> result = new ArrayList<Slice>(); |
| for (Slice slice:_slices) { |
| for (Slice.Status status:statuses) |
| if (slice.getStatus().equals(status)) |
| result.add(slice); |
| } |
| return result; |
| } |
| |
| /** |
| * Gets the master slice. |
| */ |
| public Slice getMaster() { |
| return _master; |
| } |
| |
| /** |
| * Get the configuration for given slice. |
| */ |
| public Slice getSlice(String name) { |
| for (Slice slice:_slices) |
| if (slice.getName().equals(name)) |
| return slice; |
| throw new UserException(_loc.get("slice-not-found", name, |
| getActiveSliceNames())); |
| } |
| |
| public DistributionPolicy getDistributionPolicyInstance() { |
| if (distributionPolicyPlugin.get() == null) { |
| distributionPolicyPlugin.instantiate(DistributionPolicy.class, |
| this, true); |
| } |
| return (DistributionPolicy) distributionPolicyPlugin.get(); |
| } |
| |
| public void setDistributionPolicyInstance(String val) { |
| distributionPolicyPlugin.set(val); |
| } |
| |
| public Object getConnectionFactory() { |
| if (virtualDataSource == null) { |
| DistributedDataSource ds = createDistributedDataStore(); |
| virtualDataSource = |
| DataSourceFactory.installDBDictionary( |
| getDBDictionaryInstance(), ds, this, false); |
| } |
| return virtualDataSource; |
| } |
| |
| /** |
| * Create a virtual DistributedDataSource as a composite of individual |
| * slices as per configuration, optionally ignoring slices that can not be |
| * connected. |
| */ |
| private DistributedDataSource createDistributedDataStore() { |
| List<DataSource> dataSources = new ArrayList<DataSource>(); |
| boolean isLenient = lenientPlugin.get(); |
| boolean isXA = true; |
| for (Slice slice : _slices) { |
| JDBCConfiguration conf = (JDBCConfiguration)slice.getConfiguration(); |
| Log log = conf.getConfigurationLog(); |
| String url = getConnectionInfo(conf); |
| if (log.isInfoEnabled()) |
| log.info(_loc.get("slice-connect", slice, url)); |
| try { |
| DataSource ds = DataSourceFactory.newDataSource(conf, false); |
| DecoratingDataSource dds = new DecoratingDataSource(ds); |
| ds = DataSourceFactory.installDBDictionary( |
| conf.getDBDictionaryInstance(), dds, conf, false); |
| if (verifyDataSource(slice, ds)) { |
| dataSources.add(ds); |
| isXA &= isXACompliant(ds); |
| } |
| } catch (Throwable ex) { |
| handleBadConnection(isLenient, slice, ex); |
| } |
| } |
| if (dataSources.isEmpty()) |
| throw new UserException(_loc.get("no-slice")); |
| DistributedDataSource result = new DistributedDataSource(dataSources); |
| return result; |
| } |
| |
| String getConnectionInfo(OpenJPAConfiguration conf) { |
| String result = conf.getConnectionURL(); |
| if (result == null) { |
| result = conf.getConnectionDriverName(); |
| String props = conf.getConnectionProperties(); |
| if (props != null) |
| result += "(" + props + ")"; |
| } |
| return result; |
| } |
| |
| boolean isXACompliant(DataSource ds) { |
| if (ds instanceof DelegatingDataSource) |
| return ((DelegatingDataSource) ds).getInnermostDelegate() |
| instanceof XADataSource; |
| return ds instanceof XADataSource; |
| } |
| |
| /** |
| * Verify that a connection can be established to the given slice. If |
| * connection can not be established then slice is set to INACTIVE state. |
| */ |
| private boolean verifyDataSource(Slice slice, DataSource ds) { |
| Connection con = null; |
| try { |
| con = ds.getConnection(); |
| slice.setStatus(Slice.Status.ACTIVE); |
| if (con == null) { |
| slice.setStatus(Slice.Status.INACTIVE); |
| return false; |
| } |
| return true; |
| } catch (SQLException ex) { |
| slice.setStatus(Slice.Status.INACTIVE); |
| return false; |
| } finally { |
| if (con != null) |
| try { |
| con.close(); |
| } catch (SQLException ex) { |
| // ignore |
| } |
| } |
| } |
| |
| /** |
| * Either throw a user exception or add the configuration to the given list, |
| * based on <code>isLenient</code>. |
| */ |
| private void handleBadConnection(boolean isLenient, Slice slice, |
| Throwable ex) { |
| OpenJPAConfiguration conf = slice.getConfiguration(); |
| String url = conf.getConnectionURL(); |
| Log log = getLog(LOG_RUNTIME); |
| if (isLenient) { |
| if (ex != null) { |
| log.warn(_loc.get("slice-connect-known-warn", slice, url, ex |
| .getCause())); |
| } else { |
| log.warn(_loc.get("slice-connect-warn", slice, url)); |
| } |
| } else if (ex != null) { |
| throw new UserException(_loc.get("slice-connect-known-error", |
| slice, url, ex), ex.getCause()); |
| } else { |
| throw new UserException(_loc.get("slice-connect-error", slice, url)); |
| } |
| } |
| |
| /** |
| * Create individual slices with configurations from the given properties. |
| */ |
| void setSlices(Map original) { |
| List<String> sliceNames = findSlices(original); |
| Log log = getConfigurationLog(); |
| if (sliceNames.isEmpty()) { |
| throw new UserException(_loc.get("slice-none-configured")); |
| } |
| String unit = getPersistenceUnitName(original); |
| for (String key : sliceNames) { |
| JDBCConfiguration child = new JDBCConfigurationImpl(); |
| child.fromProperties(createSliceProperties(original, key)); |
| child.setId(unit+DOT+key); |
| Slice slice = new Slice(key, child); |
| _slices.add(slice); |
| if (log.isTraceEnabled()) |
| log.trace(_loc.get("slice-configuration", key, child |
| .toProperties(false))); |
| } |
| setMaster(original); |
| } |
| |
| /** |
| * Finds the slices. If <code>openjpa.slice.Names</code> property is |
| * specified then the slices are ordered in the way they are listed. |
| * Otherwise scans all available slices by looking for property of the form |
| * <code>openjpa.slice.XYZ.abc</code> where <code>XYZ</code> is the slice |
| * identifier and <code>abc</code> is any openjpa property name. The slices |
| * are then ordered alphabetically by their identifier. |
| */ |
| private List<String> findSlices(Map p) { |
| List<String> sliceNames = new ArrayList<String>(); |
| |
| Log log = getConfigurationLog(); |
| String key = PREFIX_SLICE + namesPlugin.getProperty(); |
| boolean explicit = p.containsKey(key); |
| if (explicit) { |
| String[] values = p.get(key).toString().split("\\,"); |
| for (String name:values) |
| if (!sliceNames.contains(name.trim())) |
| sliceNames.add(name.trim()); |
| } else { |
| if (log.isWarnEnabled()) |
| log.warn(_loc.get("no-slice-names", key)); |
| sliceNames = scanForSliceNames(p); |
| Collections.sort(sliceNames); |
| } |
| if (log.isInfoEnabled()) { |
| log.info(_loc.get("slice-available", sliceNames)); |
| } |
| return sliceNames; |
| } |
| |
| /** |
| * Scan the given map for slice-specific property of the form |
| * <code>openjpa.slice.XYZ.abc</code> (while ignoring |
| * <code>openjpa.slice.XYZ</code> as they refer to slice-wide property) |
| * to determine the names of all available slices. |
| */ |
| private List<String> scanForSliceNames(Map p) { |
| List<String> sliceNames = new ArrayList<String>(); |
| for (Object o : p.keySet()) { |
| String key = o.toString(); |
| if (key.startsWith(PREFIX_SLICE) && getPartCount(key) > 3) { |
| String sliceName = |
| chopTail(chopHead(o.toString(), PREFIX_SLICE), DOT); |
| if (!sliceNames.contains(sliceName)) |
| sliceNames.add(sliceName); |
| } |
| } |
| return sliceNames; |
| } |
| |
| private static int getPartCount(String s) { |
| return (s == null) ? 0 : s.split(REGEX_DOT).length; |
| } |
| |
| private static String chopHead(String s, String head) { |
| if (s.startsWith(head)) |
| return s.substring(head.length()); |
| return s; |
| } |
| |
| private static String chopTail(String s, String tail) { |
| int i = s.lastIndexOf(tail); |
| if (i == -1) |
| return s; |
| return s.substring(0, i); |
| } |
| |
| /** |
| * Creates given <code>slice</code> specific configuration properties from |
| * given <code>original</code> key-value map. The rules are |
| * <LI> if key begins with <code>"slice.XXX."</code> where |
| * <code>XXX</code> is the given slice name, then replace |
| * <code>"slice.XXX.</code> with <code>openjpa.</code>. |
| * <LI>if key begins with <code>"slice."</code> but not with |
| * <code>"slice.XXX."</code>, the ignore i.e. any property of other |
| * slices or global slice property e.g. |
| * <code>slice.DistributionPolicy</code> |
| * <code>if key starts with <code>"openjpa."</code> and a corresponding |
| * <code>"slice.XXX."</code> property does not exist, then use this as |
| * default property |
| * <code>property with any other prefix is simply copied |
| * |
| */ |
| Map createSliceProperties(Map original, String slice) { |
| Map result = new Properties(); |
| String prefix = PREFIX_SLICE + slice + DOT; |
| for (Object o : original.keySet()) { |
| String key = o.toString(); |
| if (key.startsWith(prefix)) { |
| String newKey = PREFIX_OPENJPA + key.substring(prefix.length()); |
| result.put(newKey, original.get(o)); |
| } else if (key.startsWith(PREFIX_SLICE)) { |
| // ignore keys that are in 'slice.' namespace but not this slice |
| } else if (key.startsWith(PREFIX_OPENJPA)) { |
| String newKey = prefix + key.substring(PREFIX_OPENJPA.length()); |
| if (!original.containsKey(newKey)) |
| result.put(key, original.get(o)); |
| } else { // keys that are neither "openjpa" nor "slice" namespace |
| result.put(key, original.get(o)); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Determine the master slice. |
| */ |
| private void setMaster(Map original) { |
| String key = PREFIX_SLICE + masterPlugin.getProperty(); |
| Object masterSlice = original.get(key); |
| Log log = getConfigurationLog(); |
| List<Slice> activeSlices = getSlices(null); |
| if (masterSlice == null) { |
| _master = activeSlices.get(0); |
| if (log.isWarnEnabled()) |
| log.warn(_loc.get("no-master-slice", key, _master)); |
| return; |
| } |
| for (Slice slice:activeSlices) |
| if (slice.getName().equals(masterSlice)) |
| _master = slice; |
| if (_master == null) { |
| _master = activeSlices.get(0); |
| } |
| } |
| |
| public String getExecutorService() { |
| return executorServicePlugin.getString(); |
| } |
| |
| public void setExecutorService(ExecutorService txnManager) { |
| executorServicePlugin.set(txnManager); |
| } |
| |
| public ExecutorService getExecutorServiceInstance() { |
| if (executorServicePlugin.get() == null) { |
| executorServicePlugin.instantiate(ExecutorService.class, this); |
| } |
| return (ExecutorService) executorServicePlugin.get(); |
| } |
| } |