| /* |
| * 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.lib.conf; |
| |
| import java.awt.Image; |
| import java.beans.BeanDescriptor; |
| import java.beans.BeanInfo; |
| import java.beans.EventSetDescriptor; |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.MethodDescriptor; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.beans.PropertyDescriptor; |
| import java.io.Externalizable; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.io.PrintWriter; |
| import java.io.Serializable; |
| import java.io.StringWriter; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.security.AccessController; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import java.util.Objects; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| 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.log.NoneLogFactory; |
| import org.apache.openjpa.lib.util.Closeable; |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.MultiClassLoader; |
| import org.apache.openjpa.lib.util.ParseException; |
| import org.apache.openjpa.lib.util.Services; |
| import org.apache.openjpa.lib.util.StringDistance; |
| import org.apache.openjpa.lib.util.StringUtil; |
| |
| /** |
| * Default implementation of the {@link Configuration} interface. |
| * Subclasses can choose to obtain configuration |
| * information from JNDI, Properties, a Bean-builder, etc. This class |
| * provides base configuration functionality, including serialization, |
| * the <code>equals</code> and <code>hashCode</code> contracts, and default |
| * property loading. |
| * Property descriptors for {@link Value} instances are constructed from |
| * the {@link Localizer} for the package of the configuration class. The |
| * following localized strings will be used for describing a value, where |
| * <em>name</em> is the last token of the value's property string: |
| * <ul> |
| * <li><em>name</em>-name: The name that will be displayed for the |
| * option in a user interface; required.</li> |
| * <li><em>name</em>-desc: A brief description of the option; required.</li> |
| * <li><em>name</em>-type: The type or category name for this option; |
| * required.</li> |
| * <li><em>name</em>-expert: True if this is an expert option, false |
| * otherwise; defaults to false.</li> |
| * <li><em>name</em>-values: Set of expected or common values, excluding |
| * alias keys; optional.</li> |
| * <li><em>name</em>-interface: The class name of an interface whose |
| * discoverable implementations should be included in the set of expected |
| * or common values; optional.</li> |
| * <li><em>name</em>-cat: The hierarchical category for the property |
| * name, separated by ".". |
| * <li><em>name</em>-displayorder: The order in which the property should |
| * be displayer.</li> |
| * </ul> |
| * |
| * @author Abe White |
| */ |
| public class ConfigurationImpl |
| implements Configuration, Externalizable, ValueListener { |
| |
| private static final String SEP = J2DoPrivHelper.getLineSeparator(); |
| |
| private static final Localizer _loc = Localizer.forPackage(ConfigurationImpl.class); |
| |
| public ObjectValue logFactoryPlugin; |
| public StringValue id; |
| |
| private String _product = null; |
| private int _readOnlyState = INIT_STATE_LIQUID; |
| private Map _props = null; |
| private boolean _globals = false; |
| private String _auto = null; |
| private final List<Value> _vals = new ArrayList<>(); |
| private Set<String> _supportedKeys = new TreeSet<>(); |
| |
| // property listener helper |
| private PropertyChangeSupport _changeSupport = null; |
| |
| // cache descriptors |
| private PropertyDescriptor[] _pds = null; |
| private MethodDescriptor[] _mds = null; |
| |
| // An additional (and optional) classloader to load custom plugins. |
| private ClassLoader _userCL; |
| |
| //Ant task needs to defer the resource loading |
| //until the classpath setting is loaded properly |
| private boolean _deferResourceLoading = false; |
| |
| |
| /** |
| * Default constructor. Attempts to load default properties through |
| * system's configured {@link ProductDerivation}s. |
| */ |
| public ConfigurationImpl() { |
| this(true); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param loadGlobals whether to attempt to load the global properties |
| */ |
| public ConfigurationImpl(boolean loadGlobals) { |
| setProductName("openjpa"); |
| |
| logFactoryPlugin = addPlugin("Log", true); |
| String[] aliases = new String[]{ |
| "true", LogFactoryImpl.class.getName(), |
| "openjpa", LogFactoryImpl.class.getName(), |
| "commons", "org.apache.openjpa.lib.log.CommonsLogFactory", |
| "log4j", "org.apache.openjpa.lib.log.Log4JLogFactory", |
| "slf4j", "org.apache.openjpa.lib.log.SLF4JLogFactory", |
| "jul", "org.apache.openjpa.lib.log.JULLogFactory", |
| "none", NoneLogFactory.class.getName(), |
| "false", NoneLogFactory.class.getName(), |
| }; |
| logFactoryPlugin.setAliases(aliases); |
| logFactoryPlugin.setDefault(aliases[0]); |
| logFactoryPlugin.setString(aliases[0]); |
| logFactoryPlugin.setInstantiatingGetter("getLogFactory"); |
| |
| id = addString("Id"); |
| |
| if (loadGlobals) |
| loadGlobals(); |
| } |
| |
| /** |
| * Automatically load global values from the system's |
| * {@link ProductDerivation}s, and from System properties. |
| */ |
| public boolean loadGlobals() { |
| MultiClassLoader loader = AccessController |
| .doPrivileged(J2DoPrivHelper.newMultiClassLoaderAction()); |
| loader.addClassLoader(AccessController.doPrivileged( |
| J2DoPrivHelper.getContextClassLoaderAction())); |
| loader.addClassLoader(getClass().getClassLoader()); |
| ConfigurationProvider provider = ProductDerivations.loadGlobals(loader); |
| if (provider != null) |
| provider.setInto(this); |
| |
| // let system properties override other globals |
| try { |
| Properties systemProperties = AccessController.doPrivileged( |
| J2DoPrivHelper.getPropertiesAction()); |
| HashMap sysPropHM = null; |
| synchronized(systemProperties) { |
| // Prevent concurrent modification of systemProperties until HashMap ctor is completed. |
| sysPropHM = new HashMap(systemProperties); |
| } |
| fromProperties(sysPropHM); |
| } catch (SecurityException se) { |
| // security manager might disallow |
| } |
| |
| _globals = true; |
| if (provider == null) { |
| Log log = getConfigurationLog(); |
| if (log.isTraceEnabled()) |
| log.trace(_loc.get("no-default-providers")); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public String getProductName() { |
| return _product; |
| } |
| |
| @Override |
| public void setProductName(String name) { |
| _product = name; |
| } |
| |
| @Override |
| public LogFactory getLogFactory() { |
| if (logFactoryPlugin.get() == null) |
| logFactoryPlugin.instantiate(LogFactory.class, this); |
| return (LogFactory) logFactoryPlugin.get(); |
| } |
| |
| @Override |
| public void setLogFactory(LogFactory logFactory) { |
| logFactoryPlugin.set(logFactory); |
| } |
| |
| @Override |
| public String getLog() { |
| return logFactoryPlugin.getString(); |
| } |
| |
| @Override |
| public void setLog(String log) { |
| logFactoryPlugin.setString(log); |
| } |
| |
| @Override |
| public Log getLog(String category) { |
| return getLogFactory().getLog(category); |
| } |
| |
| @Override |
| public String getId() { |
| return id.get(); |
| } |
| |
| @Override |
| public void setId(String id) { |
| this.id.set(id); |
| } |
| |
| /** |
| * Returns the logging channel <code>openjpa.Runtime</code> by default. |
| */ |
| @Override |
| public Log getConfigurationLog() { |
| return getLog("openjpa.Runtime"); |
| } |
| |
| @Override |
| public Value[] getValues() { |
| return (Value[]) _vals.toArray(new Value[_vals.size()]); |
| } |
| |
| /** |
| * Gets the registered Value for the given propertyName. |
| * |
| * @param property can be either fully-qualified name or the simple name |
| * with which the value has been registered. A value may have multiple |
| * equivalent names and this method searches with all equivalent names. |
| */ |
| @Override |
| public Value getValue(String property) { |
| if (property == null) |
| return null; |
| |
| // search backwards so that custom values added after construction |
| // are found quickly, since this will be the std way of accessing them |
| for (int i = _vals.size()-1; i >= 0; i--) { |
| if (_vals.get(i).matches(property)) |
| return _vals.get(i); |
| } |
| return null; |
| } |
| |
| @Override |
| public void setReadOnly(int newState) { |
| if (newState >= _readOnlyState) { |
| _readOnlyState = newState; |
| } |
| } |
| |
| public boolean isDeferResourceLoading() { |
| return _deferResourceLoading; |
| } |
| |
| public void setDeferResourceLoading(boolean deferResourceLoading) { |
| this._deferResourceLoading = deferResourceLoading; |
| } |
| |
| @Override |
| public void instantiateAll() { |
| StringWriter errs = null; |
| PrintWriter stack = null; |
| String getterName; |
| Method getter; |
| Object getterTarget; |
| for(Value val : _vals) { |
| getterName = val.getInstantiatingGetter(); |
| if (getterName == null) |
| continue; |
| |
| getterTarget = this; |
| if (getterName.startsWith("this.")) { |
| getterName = getterName.substring("this.".length()); |
| getterTarget = val; |
| } |
| |
| try { |
| getter = getterTarget.getClass().getMethod(getterName, |
| (Class[]) null); |
| getter.invoke(getterTarget, (Object[]) null); |
| } catch (Throwable t) { |
| if (t instanceof InvocationTargetException) |
| t = ((InvocationTargetException) t).getTargetException(); |
| if (errs == null) { |
| errs = new StringWriter(); |
| stack = new PrintWriter(errs); |
| } else |
| errs.write(SEP); |
| t.printStackTrace(stack); |
| stack.flush(); |
| } |
| } |
| if (errs != null) |
| throw new RuntimeException(_loc.get("get-prop-errs", |
| errs.toString()).getMessage()); |
| } |
| |
| @Override |
| public boolean isReadOnly() { |
| return _readOnlyState==INIT_STATE_FROZEN; |
| } |
| |
| @Override |
| public void addPropertyChangeListener(PropertyChangeListener listener) { |
| if (_changeSupport == null) |
| _changeSupport = new PropertyChangeSupport(this); |
| _changeSupport.addPropertyChangeListener(listener); |
| } |
| |
| @Override |
| public void removePropertyChangeListener(PropertyChangeListener listener) { |
| if (_changeSupport != null) |
| _changeSupport.removePropertyChangeListener(listener); |
| } |
| |
| @Override |
| public void valueChanged(Value val) { |
| if (_changeSupport == null && _props == null) |
| return; |
| |
| String newString = val.getString(); |
| if (_changeSupport != null) |
| _changeSupport.firePropertyChange(val.getProperty(), null, newString); |
| |
| // keep cached props up to date |
| if (_props != null) { |
| if (newString == null) |
| Configurations.removeProperty(val.getProperty(), _props); |
| else if (Configurations.containsProperty(val, _props) |
| || val.getDefault() == null |
| || !val.getDefault().equals(newString)) |
| setValue(_props, val); |
| } |
| } |
| |
| /** |
| * Closes all closeable values and plugins. |
| */ |
| @Override |
| public final void close() { |
| ProductDerivations.beforeClose(this); |
| |
| preClose(); |
| |
| ObjectValue val; |
| for(Value v : _vals) { |
| if (v instanceof Closeable) { |
| try { |
| ((Closeable)v).close(); |
| } |
| catch (Exception e) { |
| // noop |
| } |
| continue; |
| } |
| |
| if (!(v instanceof ObjectValue)) |
| continue; |
| |
| val = (ObjectValue) v; |
| if (val.get() instanceof Closeable) { |
| try { |
| ((Closeable) val.get()).close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Invoked by final method {@link #close} after invoking the |
| * {@link ProductDerivation#beforeConfigurationClose} callbacks |
| * but before performing internal close operations. |
| * |
| * @since 0.9.7 |
| */ |
| protected void preClose() { |
| } |
| |
| /////////////////////////// |
| // BeanInfo implementation |
| /////////////////////////// |
| |
| @Override |
| public BeanInfo[] getAdditionalBeanInfo() { |
| return new BeanInfo[0]; |
| } |
| |
| @Override |
| public BeanDescriptor getBeanDescriptor() { |
| return new BeanDescriptor(getClass()); |
| } |
| |
| @Override |
| public int getDefaultEventIndex() { |
| return 0; |
| } |
| |
| @Override |
| public int getDefaultPropertyIndex() { |
| return 0; |
| } |
| |
| @Override |
| public EventSetDescriptor[] getEventSetDescriptors() { |
| return new EventSetDescriptor[0]; |
| } |
| |
| @Override |
| public Image getIcon(int kind) { |
| return null; |
| } |
| |
| @Override |
| public synchronized MethodDescriptor[] getMethodDescriptors() { |
| if (_mds != null) |
| return _mds; |
| |
| PropertyDescriptor[] pds = getPropertyDescriptors(); |
| List<MethodDescriptor> descs = new ArrayList<>(); |
| for (int i = 0; i < pds.length; i++) { |
| Method write = pds[i].getWriteMethod(); |
| Method read = pds[i].getReadMethod(); |
| if (read != null && write != null) { |
| descs.add(new MethodDescriptor(write)); |
| descs.add(new MethodDescriptor(read)); |
| } |
| } |
| _mds = (MethodDescriptor[]) descs. |
| toArray(new MethodDescriptor[descs.size()]); |
| return _mds; |
| } |
| |
| @Override |
| public synchronized PropertyDescriptor[] getPropertyDescriptors() { |
| if (_pds != null) |
| return _pds; |
| |
| _pds = new PropertyDescriptor[_vals.size()]; |
| |
| List<String> failures = null; |
| Value val; |
| for (int i = 0; i < _vals.size(); i++) { |
| val = (Value) _vals.get(i); |
| try { |
| _pds[i] = getPropertyDescriptor(val); |
| } catch (MissingResourceException mre) { |
| if (failures == null) |
| failures = new ArrayList<>(); |
| failures.add(val.getProperty()); |
| } catch (IntrospectionException ie) { |
| if (failures == null) |
| failures = new ArrayList<>(); |
| failures.add(val.getProperty()); |
| } |
| } |
| if (failures != null) |
| throw new ParseException(_loc.get("invalid-property-descriptors", |
| failures)); |
| |
| return _pds; |
| } |
| |
| /** |
| * Create a property descriptor for the given value. |
| */ |
| private PropertyDescriptor getPropertyDescriptor(Value val) |
| throws IntrospectionException { |
| String prop = val.getProperty(); |
| prop = prop.substring(prop.lastIndexOf('.') + 1); |
| |
| // set up property descriptor |
| PropertyDescriptor pd; |
| try { |
| pd = new PropertyDescriptor(Introspector.decapitalize(prop), |
| getClass()); |
| } catch (IntrospectionException ie) { |
| // if there aren't any methods for this value(i.e., if it's a |
| // dynamically-added value), then an IntrospectionException will |
| // be thrown. Try to create a PD with no read or write methods. |
| pd = new PropertyDescriptor(Introspector.decapitalize(prop), |
| (Method) null, (Method) null); |
| } |
| pd.setDisplayName(findLocalized(prop + "-name", true, val.getScope())); |
| pd.setShortDescription(findLocalized(prop + "-desc", true, |
| val.getScope())); |
| pd.setExpert("true".equals(findLocalized(prop + "-expert", false, |
| val.getScope()))); |
| |
| try { |
| pd.setReadMethod(getClass().getMethod("get" |
| + StringUtil.capitalize(prop), (Class[]) null)); |
| pd.setWriteMethod(getClass().getMethod("set" |
| + StringUtil.capitalize(prop), new Class[] |
| { pd.getReadMethod().getReturnType() })); |
| } catch (Throwable t) { |
| // if an error occurs, it might be because the value is a |
| // dynamic property. |
| } |
| |
| String type = findLocalized(prop + "-type", true, val.getScope()); |
| if (type != null) |
| pd.setValue(ATTRIBUTE_TYPE, type); |
| |
| String cat = findLocalized(prop + "-cat", false, val.getScope()); |
| if (cat != null) |
| pd.setValue(ATTRIBUTE_CATEGORY, cat); |
| |
| pd.setValue(ATTRIBUTE_XML, toXMLName(prop)); |
| |
| String order = findLocalized(prop + "-displayorder", false, |
| val.getScope()); |
| if (order != null) |
| pd.setValue(ATTRIBUTE_ORDER, order); |
| |
| // collect allowed values from alias keys, listed values, and |
| // interface implementors |
| Collection<String> allowed = new TreeSet<>(); |
| List<String> aliases = Collections.emptyList(); |
| if (val.getAliases() != null) { |
| aliases = Arrays.asList(val.getAliases()); |
| for (int i = 0; i < aliases.size(); i += 2) |
| allowed.add(aliases.get(i)); |
| } |
| String[] vals = StringUtil.split(findLocalized(prop |
| + "-values", false, val.getScope()), ",", 0); |
| for (int i = 0; i < vals.length; i++) |
| if (!aliases.contains(vals[i])) |
| allowed.add(vals[i]); |
| try { |
| Class<?> intf = Class.forName(findLocalized(prop |
| + "-interface", true, val.getScope()), false, |
| getClass().getClassLoader()); |
| pd.setValue(ATTRIBUTE_INTERFACE, intf.getName()); |
| String[] impls = Services.getImplementors(intf); |
| for (int i = 0; i < impls.length; i++) |
| if (!aliases.contains(impls[i])) |
| allowed.add(impls[i]); |
| } catch (Throwable t) { |
| } |
| if (!allowed.isEmpty()) |
| pd.setValue(ATTRIBUTE_ALLOWED_VALUES, (String[]) allowed.toArray |
| (new String[allowed.size()])); |
| |
| return pd; |
| } |
| |
| /** |
| * Find the given localized string, or return null if not found. |
| */ |
| @SuppressWarnings("unchecked") |
| private String findLocalized(String key, boolean fatal, Class<?> scope) { |
| // find the localizer package that contains this key |
| Localizer loc = null; |
| |
| // check the package that the value claims to be defined in, if |
| // available, before we start guessing. |
| if (scope != null) { |
| loc = Localizer.forPackage(scope); |
| try { |
| return loc.getFatal(key).getMessage(); |
| } catch (MissingResourceException mse) { |
| } |
| } |
| |
| for (Class cls = getClass(); cls != Object.class; |
| cls = cls.getSuperclass()) { |
| loc = Localizer.forPackage(cls); |
| try { |
| return loc.getFatal(key).getMessage(); |
| } catch (MissingResourceException mse) { |
| } |
| } |
| |
| if (fatal) |
| throw new MissingResourceException(key, getClass().getName(), key); |
| return null; |
| } |
| |
| //////////////// |
| // To/from maps |
| //////////////// |
| |
| /** |
| * An internal method to retrieve properties, to support 2 public methods, |
| * getAllProperties() and toProperties(boolean). |
| * |
| * @param storeDefaults |
| * whether or not to retrieve a property if its value is the |
| * default value. |
| */ |
| @Override |
| public Map toProperties(boolean storeDefaults) { |
| // clone properties before making any modifications; we need to keep |
| // the internal properties instance consistent to maintain equals and |
| // hashcode contracts |
| Map<String, String> clone; |
| if (_props == null) |
| clone = new TreeMap<>(); |
| else if (_props instanceof Properties) |
| clone = (Map) ((Properties) _props).clone(); |
| else |
| clone = new TreeMap<String, String>(_props); |
| |
| // if no existing properties or the properties should contain entries |
| // with default values, add values to properties |
| if (_props == null || storeDefaults) { |
| String str; |
| for (Value val : _vals) { |
| // NOTE: Following was removed to hide Value.INVISIBLE properties, like connectionPassword |
| // if key in existing properties, we already know value is up to date |
| //if (_props != null && Configurations.containsProperty(val, _props) && val.isVisible()) |
| // continue; |
| str = val.getString(); |
| if ((str != null && (storeDefaults || !str.equals(val.getDefault())))) |
| setValue(clone, val); |
| } |
| if (_props == null) |
| _props = new TreeMap(clone); |
| } |
| return clone; |
| } |
| |
| @Override |
| public void fromProperties(Map map) { |
| if (map == null || map.isEmpty()) |
| return; |
| if (isReadOnly()) |
| throw new IllegalStateException(_loc.get("read-only").getMessage()); |
| |
| // if the only previous call was to load defaults, forget them. |
| // this way we preserve the original formatting of the user's props |
| // instead of the defaults. this is important for caching on |
| // configuration objects |
| if (_globals) { |
| _props = null; |
| _globals = false; |
| } |
| |
| // copy the input to avoid mutation issues |
| if (map instanceof HashMap) |
| map = (Map) ((HashMap) map).clone(); |
| else if (map instanceof Properties) |
| map = (Map) ((Properties) map).clone(); |
| else |
| map = new LinkedHashMap(map); |
| |
| Map remaining = new HashMap(map); |
| boolean ser = true; |
| Object o; |
| for (Value val : _vals) { |
| o = findValue(map, val); |
| if (o == null) |
| continue; |
| if (o instanceof String) { |
| // OPENJPA-1830 Do not overwrite existing string values with "******" |
| if ((!Objects.equals((String) o, val.getString())) && |
| (!Objects.equals((String) o, Value.INVISIBLE))) |
| val.setString((String) o); |
| } else { |
| ser &= o instanceof Serializable; |
| val.setObject(o); |
| } |
| Configurations.removeProperty(val.getProperty(), remaining); |
| } |
| |
| // convention is to point product at a resource with the |
| // <prefix>.properties System property; remove that property so we |
| // we don't warn about it |
| Configurations.removeProperty("properties", remaining); |
| Configurations.removeProperty("Id", remaining, map); |
| |
| // now warn if there are any remaining properties that there |
| // is an unhandled prop, and remove the unknown properties |
| Map.Entry entry; |
| for (Iterator itr = remaining.entrySet().iterator(); itr.hasNext();) { |
| entry = (Map.Entry) itr.next(); |
| Object key = entry.getKey(); |
| if (key != null) { |
| warnInvalidProperty((String) key); |
| map.remove(key); |
| } |
| } |
| |
| // cache properties |
| if (_props == null && ser) |
| _props = map; |
| } |
| |
| @Override |
| public List<String> getPropertyKeys(String propertyName) { |
| Value value = getValue(propertyName); |
| return value == null ? Collections.EMPTY_LIST : value.getPropertyKeys(); |
| } |
| |
| /** |
| * Gets all known property keys. |
| * The keys are harvested from the property names (including the equivalent names) of the registered values. |
| * A key may be prefixed if the corresponding property name was without a prefix. |
| * @see #fixPrefix(String) |
| * The Values that are {@linkplain Value#makePrivate() marked private} are filtered out. |
| */ |
| @Override |
| public Set<String> getPropertyKeys() { |
| synchronized (_supportedKeys) { |
| if (_supportedKeys.size() == 0) { |
| for (Value val : _vals) { |
| if (val.isPrivate()) |
| continue; |
| List<String> keys = val.getPropertyKeys(); |
| for (String key : keys) { |
| _supportedKeys.add(fixPrefix(key)); |
| } |
| } |
| } |
| } |
| //OJ2257: Return a copy of _supportedKeys as calls to this method (e.g. |
| //BrokerImpl.getSupportedProperties()) may add to this set. |
| return new TreeSet<>(_supportedKeys); |
| } |
| |
| /** |
| * Adds a prefix <code>"openjpa."</code> to the given key, if necessary. A key is |
| * considered without prefix if it starts neither of <code>"openjpa."</code>, |
| * <code>"java."</code> and <code>"javax."</code>. |
| */ |
| String fixPrefix(String key) { |
| return (key == null || hasKnownPrefix(key)) ? key : "openjpa."+key; |
| } |
| |
| boolean hasKnownPrefix(String key) { |
| String[] prefixes = ProductDerivations.getConfigurationPrefixes(); |
| for (String prefix : prefixes) { |
| if (key.startsWith(prefix)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Adds <code>o</code> to <code>map</code> under key for <code>val</code>. |
| * Use this method instead of attempting to add the value directly because |
| * this will account for the property prefix. |
| */ |
| private void setValue(Map map, Value val) { |
| Object key = val.getLoadKey(); |
| if (key == null) { |
| List<String> keys = val.getPropertyKeys(); |
| for (String k : keys) { |
| if (hasKnownPrefix(k)) { |
| key = k; |
| break; |
| } |
| } |
| if (key == null) { |
| key = "openjpa." + val.getProperty(); |
| } |
| } |
| Object external = val.isHidden() ? Value.INVISIBLE : |
| val instanceof ObjectValue ? val.getString() : val.get(); |
| map.put(key, external); |
| } |
| |
| /** |
| * Look up the given value, testing all available prefixes and all possible |
| * property names. Detects if the given map contains multiple keys that |
| * are equivalent names for the given value. |
| */ |
| private Object findValue(Map map, Value val) { |
| Object result = null; |
| List<String> partialKeys = val.getPropertyKeys(); |
| for (String partialKey : partialKeys) { |
| String key = ProductDerivations.getConfigurationKey( |
| partialKey, map); |
| if (map.containsKey(key)) { |
| // do not return immediately. Looping through all equivalent |
| // property names will detect if the Map contains multiple keys |
| // that are equivalent as it tries to set load key. |
| val.setLoadKey(key); |
| result = map.get(key); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Issue a warning that the specified property is not valid. |
| */ |
| private void warnInvalidProperty(String propName) { |
| if (propName != null && |
| (propName.startsWith("java.") || propName.startsWith("javax.persistence")|| propName.startsWith("sun."))) |
| return; |
| if (!isInvalidProperty(propName)) |
| return; |
| Log log = getConfigurationLog(); |
| if (log == null || !log.isWarnEnabled()) |
| return; |
| |
| // try to find the closest string to the invalid property |
| // so that we can provide a hint in case of a misspelling |
| String closest = StringDistance.getClosestLevenshteinDistance |
| (propName, newPropertyList(), 15); |
| |
| if (closest == null) |
| log.warn(_loc.get("invalid-property", propName)); |
| else |
| log.warn(_loc.get("invalid-property-hint", propName, closest)); |
| } |
| |
| /** |
| * Return a comprehensive list of recognized map keys. |
| */ |
| private Collection<String> newPropertyList() { |
| String[] prefixes = ProductDerivations.getConfigurationPrefixes(); |
| List<String> l = new ArrayList<>(_vals.size() * prefixes.length); |
| for(Value v : _vals) { |
| for (int j = 0; j < prefixes.length; j++) |
| l.add(prefixes[j] + "." + v.getProperty()); |
| } |
| return l; |
| } |
| |
| /** |
| * Returns true if the specified property name should raise a warning |
| * if it is not found in the list of known properties. |
| */ |
| protected boolean isInvalidProperty(String propName) { |
| // handle warnings for openjpa.SomeString, but not for |
| // openjpa.some.subpackage.SomeString, since it might be valid for some |
| // specific implementation of OpenJPA |
| String[] prefixes = ProductDerivations.getConfigurationPrefixes(); |
| for (String prefix : prefixes) { |
| if (propName.toLowerCase(Locale.ENGLISH).startsWith(prefix) |
| && propName.length() > prefix.length() + 1 |
| && propName.indexOf('.', prefix.length()) == prefix.length() |
| && propName.indexOf('.', prefix.length() + 1) == -1 |
| && "openjpa".equals(prefix)) |
| return true; |
| } |
| return false; |
| } |
| |
| ////////////////////// |
| // Auto-configuration |
| ////////////////////// |
| |
| /** |
| * This method loads the named resource as a properties file. It is |
| * useful for auto-configuration tools so users can specify a |
| * <code>properties</code> value with the name of a resource. |
| */ |
| public void setProperties(String resourceName) throws IOException { |
| |
| if (!_deferResourceLoading) { |
| String anchor = null; |
| if (resourceName.indexOf("#") != -1) { |
| anchor = resourceName.substring(resourceName.lastIndexOf("#") + 1); |
| resourceName = resourceName.substring(0, resourceName.length() - anchor.length() - 1); |
| } |
| |
| ProductDerivations.load(resourceName, anchor, getClass().getClassLoader()).setInto(this); |
| } |
| |
| _auto = resourceName; |
| } |
| |
| /** |
| * This method loads the named file as a properties file. It is |
| * useful for auto-configuration tools so users can specify a |
| * <code>propertiesFile</code> value with the name of a file. |
| */ |
| public void setPropertiesFile(File file) throws IOException { |
| ProductDerivations.load(file, null, getClass().getClassLoader()). |
| setInto(this); |
| setDeferResourceLoading(false); |
| _auto = file.toString(); |
| } |
| |
| /** |
| * Return the resource that was set via auto-configuration methods |
| * {@link #setProperties} or {@link #setPropertiesFile}, or null if none. |
| */ |
| public String getPropertiesResource() { |
| return _auto; |
| } |
| |
| ///////////// |
| // Utilities |
| ///////////// |
| |
| /** |
| * Performs an equality check based on equality of values. |
| * {@link Value#equals(Object) Equality} of Values varies if the Value is |
| * {@link Value#isDynamic() dynamic}. |
| */ |
| @Override |
| public boolean equals(Object other) { |
| if (other == this) |
| return true; |
| if (other == null) |
| return false; |
| if (!getClass().equals(other.getClass())) |
| return false; |
| |
| // compare properties |
| ConfigurationImpl conf = (ConfigurationImpl) other; |
| if (_vals.size() != conf.getValues().length) |
| return false; |
| for(Value v : _vals) { |
| String propName = v.getProperty(); |
| Value thisV = this.getValue(propName); |
| Value thatV = conf.getValue(propName); |
| if (!thisV.equals(thatV)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Computes hash code based on the hashCodes of the values. |
| * {@link Value#hashCode() HashCode} of a Value varies if the Value is |
| * {@link Value#isDynamic() dynamic}. |
| */ |
| @Override |
| public int hashCode() { |
| int hash = 0; |
| for(Value v : _vals) { |
| hash += v.hashCode(); |
| } |
| return hash; |
| } |
| |
| /** |
| * Convert <code>propName</code> to a lowercase-with-hyphens-style string. |
| * This algorithm is only designed for mixes of uppercase and lowercase |
| * letters and lone digits. A more sophisticated conversion should probably |
| * be handled by a proper parser generator or regular expressions. |
| */ |
| public static String toXMLName(String propName) { |
| if (propName == null) |
| return null; |
| StringBuilder buf = new StringBuilder(); |
| char c; |
| for (int i = 0; i < propName.length(); i++) { |
| c = propName.charAt(i); |
| |
| // convert sequences of all-caps to downcase with dashes around |
| // them. put a trailing cap that is followed by downcase into the |
| // downcase word. |
| if (i != 0 && Character.isUpperCase(c) |
| && (Character.isLowerCase(propName.charAt(i-1)) |
| || (i > 1 && i < propName.length() - 1 |
| && Character.isUpperCase(propName.charAt(i-1)) |
| && Character.isLowerCase(propName.charAt(i+1))))) |
| buf.append('-'); |
| |
| // surround sequences of digits with dashes. |
| if (i != 0 |
| && ((!Character.isLetter(c) && Character.isLetter(propName |
| .charAt(i - 1))) |
| || (Character.isLetter(c) && !Character.isLetter(propName |
| .charAt(i - 1))))) |
| buf.append('-'); |
| |
| buf.append(Character.toLowerCase(c)); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Implementation of the {@link Externalizable} interface to read from |
| * the properties written by {@link #writeExternal}. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public void readExternal(ObjectInput in) |
| throws IOException, ClassNotFoundException { |
| fromProperties((Map) in.readObject()); |
| _props = (Map) in.readObject(); |
| _globals = in.readBoolean(); |
| } |
| |
| /** |
| * Implementation of the {@link Externalizable} interface to write |
| * the properties returned by {@link #toProperties}. |
| */ |
| @Override |
| public void writeExternal(ObjectOutput out) throws IOException { |
| out.writeObject(toProperties(true)); |
| out.writeObject(_props); |
| out.writeBoolean(_globals); |
| } |
| |
| /** |
| * Uses {@link #toProperties} and {@link #fromProperties} to clone |
| * configuration. |
| */ |
| @Override |
| public Object clone() { |
| try { |
| Constructor cons = getClass().getConstructor |
| (new Class[]{ boolean.class }); |
| ConfigurationImpl clone = (ConfigurationImpl) cons.newInstance |
| (new Object[]{ Boolean.FALSE }); |
| clone.fromProperties(toProperties(true)); |
| clone._props = (_props == null) ? null : new HashMap(_props); |
| clone._globals = _globals; |
| return clone; |
| } catch (RuntimeException re) { |
| throw re; |
| } catch (Exception e) { |
| throw new ParseException(e); |
| } |
| } |
| |
| @Override |
| public boolean removeValue(Value val) { |
| if (!_vals.remove(val)) |
| return false; |
| val.removeListener(this); |
| return true; |
| } |
| |
| @Override |
| public <T extends Value> T addValue(T val) { |
| _vals.add(val); |
| val.addListener(this); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public StringValue addString(String property) { |
| StringValue val = new StringValue(property); |
| addValue(val); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public FileValue addFile(String property) { |
| FileValue val = new FileValue(property); |
| addValue(val); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public IntValue addInt(String property) { |
| IntValue val = new IntValue(property); |
| addValue(val); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public DoubleValue addDouble(String property) { |
| DoubleValue val = new DoubleValue(property); |
| addValue(val); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public BooleanValue addBoolean(String property) { |
| BooleanValue val = new BooleanValue(property); |
| addValue(val); |
| val.setDefault("false"); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public StringListValue addStringList(String property) { |
| StringListValue val = new StringListValue(property); |
| addValue(val); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public ObjectValue addObject(String property) { |
| ObjectValue val = new ObjectValue(property); |
| addValue(val); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public PluginValue addPlugin(String property, boolean singleton) { |
| PluginValue val = new PluginValue(property, singleton); |
| addValue(val); |
| return val; |
| } |
| |
| /** |
| * Add the given value to the set of configuration properties. |
| */ |
| public PluginListValue addPluginList(String property) { |
| PluginListValue val = new PluginListValue(property); |
| addValue(val); |
| return val; |
| } |
| |
| @Override |
| public ClassLoader getUserClassLoader() { |
| return _userCL; |
| } |
| |
| @Override |
| public void setUserClassLoader(ClassLoader cl) { |
| _userCL = cl; |
| } |
| } |