All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openjpa.slice.jdbc.DistributedJDBCConfigurationImpl Maven / Gradle / Ivy

The newest version!
/*
 * 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.lang.reflect.Method;
import java.security.AccessController;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

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.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.slice.DistributedBrokerImpl;
import org.apache.openjpa.slice.DistributionPolicy;
import org.apache.openjpa.slice.FinderTargetPolicy;
import org.apache.openjpa.slice.ProductDerivation;
import org.apache.openjpa.slice.QueryTargetPolicy;
import org.apache.openjpa.slice.ReplicationPolicy;
import org.apache.openjpa.slice.Slice;
import org.apache.openjpa.util.UserException;

/**
 * A specialized configuration embodies a set of Slice configurations.
 * 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 _slices = new ArrayList<>();
    private Slice _master;

    private DistributedDataSource virtualDataSource;

    protected BooleanValue lenientPlugin;
    protected StringValue masterPlugin;
    protected StringListValue namesPlugin;
    public PluginValue distributionPolicyPlugin;
    public PluginValue replicationPolicyPlugin;
    public PluginValue queryTargetPolicyPlugin;
    public PluginValue finderTargetPolicyPlugin;
    public StringListValue replicatedTypesPlugin;

    private ReplicatedTypeRepository _replicationRepos;

    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);

    /**
     * Create a configuration and declare the plug-ins.
     */
    public DistributedJDBCConfigurationImpl() {
        super(true,   // load derivations
              false); // load globals
        brokerPlugin.setString(DistributedBrokerImpl.class.getName());

        distributionPolicyPlugin = addPlugin(PREFIX_SLICE + "DistributionPolicy", true);
        distributionPolicyPlugin.setAlias("random", DistributionPolicy.Default.class.getName());
        distributionPolicyPlugin.setDefault("random");
        distributionPolicyPlugin.setString("random");
        distributionPolicyPlugin.setDynamic(true);

        replicationPolicyPlugin = addPlugin(PREFIX_SLICE + "ReplicationPolicy", true);
        replicationPolicyPlugin.setAlias("all", ReplicationPolicy.Default.class.getName());
        replicationPolicyPlugin.setDefault("all");
        replicationPolicyPlugin.setString("all");
        replicationPolicyPlugin.setDynamic(true);

        queryTargetPolicyPlugin = addPlugin(PREFIX_SLICE + "QueryTargetPolicy", true);
        queryTargetPolicyPlugin.setDynamic(true);

        finderTargetPolicyPlugin = addPlugin(PREFIX_SLICE + "FinderTargetPolicy", true);
        finderTargetPolicyPlugin.setDynamic(true);

        replicatedTypesPlugin = new StringListValue(PREFIX_SLICE + "ReplicatedTypes");
        addValue(replicatedTypesPlugin);

        lenientPlugin = addBoolean(PREFIX_SLICE + "Lenient");
        lenientPlugin.setDefault("true");

        masterPlugin  = addString(PREFIX_SLICE + "Master");
        namesPlugin   = addStringList(PREFIX_SLICE + "Names");
    }

    /**
     * Configure itself as well as underlying slices.
     *
     */
    public DistributedJDBCConfigurationImpl(ConfigurationProvider cp) {
        this();
        cp.setInto(this);
        setDiagnosticContext(this);
    }

    private void setDiagnosticContext(OpenJPAConfiguration conf) {
        LogFactory logFactory = conf.getLogFactory();
        try {
            Method setter = AccessController.doPrivileged(J2DoPrivHelper.
                    getDeclaredMethodAction(logFactory.getClass(),
                    "setDiagnosticContext", new Class[]{String.class}));
            setter.invoke(logFactory, conf.getId());
        } catch (Throwable t) {
            // no contextual logging
        }
    }

    /**
     * Gets the name of the active slices.
     */
    @Override
    public List getActiveSliceNames() {
        List result = new ArrayList<>();
        for (Slice slice : _slices) {
           if (slice.isActive() && !result.contains(slice.getName()))
              result.add(slice.getName());
        }
        return result;
    }

    /**
     * Gets the name of the available slices.
     */
    @Override
    public List getAvailableSliceNames() {
        List result = new ArrayList<>();
        for (Slice slice : _slices)
            result.add(slice.getName());
        return result;
    }

    /**
     * Gets the slices of given status. Null returns all irrespective of status.
     */
    @Override
    public List getSlices(Slice.Status...statuses) {
        if (statuses == null)
            return _slices == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(_slices);
        List result = new ArrayList<>();
        for (Slice slice:_slices) {
            for (Slice.Status status:statuses)
                if (slice.getStatus().equals(status))
                    result.add(slice);
        }
        return result;
    }


    @Override
    public Slice getSlice(String name) {
        return getSlice(name, false);
    }

    /**
     * Get the Slice of the given slice.
     *
     * @param mustExist if true an exception if raised if the given slice name
     * is not a valid slice.
     */
    public Slice getSlice(String name, boolean mustExist) {
        for (Slice slice : _slices)
            if (slice.getName().equals(name))
                return slice;
        if (mustExist) {
            throw new UserException(_loc.get("slice-not-found", name,
                    getActiveSliceNames()));
        }
        return null;
    }

    @Override
    public DistributionPolicy getDistributionPolicyInstance() {
        if (distributionPolicyPlugin.get() == null) {
            distributionPolicyPlugin.instantiate(DistributionPolicy.class,
                    this, true);
        }
        return (DistributionPolicy) distributionPolicyPlugin.get();
    }

    @Override
    public String getDistributionPolicy() {
        if (distributionPolicyPlugin.get() == null) {
            distributionPolicyPlugin.instantiate(DistributionPolicy.class,
                    this, true);
        }
        return distributionPolicyPlugin.getString();
    }

    @Override
    public void setDistributionPolicyInstance(DistributionPolicy policy) {
        distributionPolicyPlugin.set(policy);
    }

    @Override
    public void setDistributionPolicy(String policy) {
        distributionPolicyPlugin.setString(policy);
    }

    @Override
    public ReplicationPolicy getReplicationPolicyInstance() {
        if (replicationPolicyPlugin.get() == null) {
            replicationPolicyPlugin.instantiate(ReplicationPolicy.class,
                    this, true);
        }
        return (ReplicationPolicy) replicationPolicyPlugin.get();
    }

    @Override
    public String getReplicationPolicy() {
        if (replicationPolicyPlugin.get() == null) {
            replicationPolicyPlugin.instantiate(ReplicationPolicy.class,
                    this, true);
        }
        return replicationPolicyPlugin.getString();
    }

    @Override
    public void setReplicationPolicyInstance(ReplicationPolicy policy) {
        replicationPolicyPlugin.set(policy);
    }

    @Override
    public void setReplicationPolicy(String policy) {
        replicationPolicyPlugin.setString(policy);
    }

    @Override
    public QueryTargetPolicy getQueryTargetPolicyInstance() {
        if (queryTargetPolicyPlugin.get() == null) {
            queryTargetPolicyPlugin.instantiate(QueryTargetPolicy.class,
                    this, true);
        }
        return (QueryTargetPolicy) queryTargetPolicyPlugin.get();
    }

    @Override
    public String getQueryTargetPolicy() {
        if (queryTargetPolicyPlugin.get() == null) {
            queryTargetPolicyPlugin.instantiate(QueryTargetPolicy.class,
                    this, true);
        }
        return queryTargetPolicyPlugin.getString();
    }

    @Override
    public void setQueryTargetPolicyInstance(QueryTargetPolicy policy) {
        queryTargetPolicyPlugin.set(policy);
    }

    @Override
    public void setQueryTargetPolicy(String policy) {
        queryTargetPolicyPlugin.setString(policy);
    }

    @Override
    public FinderTargetPolicy getFinderTargetPolicyInstance() {
        if (finderTargetPolicyPlugin.get() == null) {
            finderTargetPolicyPlugin.instantiate(FinderTargetPolicy.class,
                    this, true);
        }
        return (FinderTargetPolicy) finderTargetPolicyPlugin.get();
    }

    @Override
    public String getFinderTargetPolicy() {
        if (finderTargetPolicyPlugin.get() == null) {
            finderTargetPolicyPlugin.instantiate(FinderTargetPolicy.class,
                    this, true);
        }
        return finderTargetPolicyPlugin.getString();
    }

    @Override
    public void setFinderTargetPolicyInstance(FinderTargetPolicy policy) {
        finderTargetPolicyPlugin.set(policy);
    }

    @Override
    public void setFinderTargetPolicy(String policy) {
        finderTargetPolicyPlugin.setString(policy);
    }

    @Override
    public DistributedDataSource getConnectionFactory() {
        if (virtualDataSource == null) {
            virtualDataSource = createDistributedDataStore();
            DataSourceFactory.installDBDictionary(
                getDBDictionaryInstance(), virtualDataSource, this, false);
        }
        return virtualDataSource;
    }

    public boolean isLenient() {
        return lenientPlugin.get();
    }

    public void setLenient(boolean lenient) {
        lenientPlugin.set(lenient);
    }

    public void setMaster(String master) {
        masterPlugin.set(master);
    }

    /**
     * Gets the master slice.
     */
    @Override
    public Slice getMasterSlice() {
        if (_master == null) {
            String value = masterPlugin.get();
            if (value == null) {
                _master = _slices.get(0);
            } else {
                _master = getSlice(value, true);
            }
        }
        return _master;
    }

    /**
     * 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 dataSources = new ArrayList<>();
        boolean isXA = true;
        for (Slice slice : _slices) {
            try {
                DataSource ds = createDataSource(slice);
                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;
    }

    DataSource createDataSource(Slice slice) throws Exception {
        JDBCConfiguration conf = (JDBCConfiguration)slice.getConfiguration();
        DataSource ds = (DataSource)conf.getConnectionFactory();
        if (ds == null) {
            Log log = conf.getConfigurationLog();
            String url = getConnectionInfo(conf);
            if (log.isInfoEnabled())
                log.info(_loc.get("slice-connect", slice, url));
            ds = DataSourceFactory.newDataSource(conf, false);
            DecoratingDataSource dds = new DecoratingDataSource(ds);
            ds = DataSourceFactory.installDBDictionary(
                    conf.getDBDictionaryInstance(), dds, conf, false);
        }
        verifyDataSource(slice, ds, conf);

        return ds;
    }

    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,
    		JDBCConfiguration conf) {
        Connection con = null;
        try {
            con = ds.getConnection(conf.getConnectionUserName(),
            		conf.getConnectionPassword());
            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 isLenient.
     */
    private void handleBadConnection(boolean isLenient, Slice slice, Throwable ex) {
        OpenJPAConfiguration conf = slice.getConfiguration();
        String url = conf.getConnectionURL();
        Log log = conf.getConfigurationLog();
        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 a new Slice of given name and given properties.
     *
     * @param key name of the slice to be created
     * @param original a set of properties.
     * @return a newly configured slice
     */
    private Slice newSlice(String key, Map original) {
        JDBCConfiguration child = new JDBCConfigurationImpl();
        child.fromProperties(createSliceProperties(original, key));
        child.setId(getId()+DOT+key);
        setDiagnosticContext(child);
        child.setMappingDefaults(this.getMappingDefaultsInstance());
        child.setDataCacheManager(this.getDataCacheManagerInstance());
        child.setMetaDataRepository(this.getMetaDataRepositoryInstance());
        Slice slice = new Slice(key, child);
        Log log = getConfigurationLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("slice-configuration", key, child
                    .toProperties(false)));
        return slice;
    }

    /**
     * Finds the slices. If openjpa.slice.Names 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
     * openjpa.slice.XYZ.abc where XYZ is the slice
     * identifier and abc is any openjpa property name. The slices
     * are then ordered alphabetically by their identifier.
     */
    private List findSlices(Map p) {
        List sliceNames = new ArrayList<>();

        Log log = getConfigurationLog();
        String key = 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
     * openjpa.slice.XYZ.abc (while ignoring
     * openjpa.slice.XYZ as they refer to slice-wide property)
     * to determine the names of all available slices.
     */
    private List scanForSliceNames(Map p) {
        List sliceNames = new ArrayList<>();
        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 slice specific configuration properties from
     * given original key-value map. The rules are
     * 
  • if key begins with "openjpa.slice.XXX." where * XXX is the given slice name, then replace * "openjpa.slice.XXX. with openjpa.. *
  • if key begins with "openjpa.slice." but not with * "openjpa.slice.XXX.", then ignore i.e. any property of other * slices or global slice property e.g. * openjpa.slice.DistributionPolicy *
  • if key starts with "openjpa." and a corresponding * "openjpa.slice.XXX." property does not exist, then use this as * default property *
  • 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(); Object value = original.get(key); if (value == null) continue; if (key.startsWith(prefix)) { String newKey = PREFIX_OPENJPA + key.substring(prefix.length()); result.put(newKey, value); } 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, value); } else { // keys that are neither "openjpa" nor "slice" namespace result.put(key, value); } } return result; } Slice addSlice(String name, Map newProps) { String prefix = PREFIX_SLICE + DOT + name + DOT; for (Object key : newProps.keySet()) { if (!String.class.isInstance(key) && key.toString().startsWith(prefix)) throw new UserException(_loc.get("slice-add-wrong-key", key)); } Slice slice = getSlice(name); if (slice != null) throw new UserException(_loc.get("slice-exists", name)); Map original = super.toProperties(true); original.putAll(newProps); slice = newSlice(name, original); _slices.add(slice); try { getConnectionFactory().addDataSource(createDataSource(slice)); } catch (Exception ex) { handleBadConnection(false, slice, ex); return null; } return slice; } /** * Given the properties, creates a set of individual configurations. */ @Override public void fromProperties(Map original) { super.fromProperties(original); setDiagnosticContext(this); List sliceNames = findSlices(original); for (String name : sliceNames) { Slice slice = newSlice(name, original); _slices.add(slice); } } @Override public DecoratingDataSource createConnectionFactory() { if (virtualDataSource == null) { virtualDataSource = createDistributedDataStore(); } return virtualDataSource; } @Override public boolean isReplicated(Class cls) { if (_replicationRepos == null) { _replicationRepos = new ReplicatedTypeRepository(getMetaDataRepositoryInstance(), Arrays.asList(replicatedTypesPlugin.get())); } return _replicationRepos.contains(cls); } /** * A private repository of replicated types. * * @author Pinaki Poddar * */ private static class ReplicatedTypeRepository { private Set> _replicatedTypes = new HashSet<>(); private Set> _nonreplicatedTypes = new HashSet<>(); List names; MetaDataRepository repos; ReplicatedTypeRepository(MetaDataRepository repos, List given) { names = given; this.repos = repos; } boolean contains(Class cls) { if (_replicatedTypes.contains(cls)) return true; if (_nonreplicatedTypes.contains(cls)) return false; ClassMetaData meta = repos.getMetaData(cls, null, false); if (meta == null) { _nonreplicatedTypes.add(cls); return false; } boolean replicated = names.contains(meta.getDescribedType().getName()); if (replicated) { _replicatedTypes.add(cls); } else { _nonreplicatedTypes.add(cls); } return replicated; } } }




  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy