blob: 9e8c509a398e90f3aaef9d5b2c0fd6cf2dda76f4 [file] [log] [blame]
/*
* 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.io.File;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeSet;
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.Services;
import org.apache.openjpa.lib.util.StringUtil;
/**
* Utilities for running product derivations.
*
* @author Abe White
* @author Pinaki Poddar
*/
public class ProductDerivations {
private static final Localizer _loc = Localizer.forPackage
(ProductDerivations.class);
private static final ProductDerivation[] _derivations;
private static final String[] _derivationNames;
private static final Throwable[] _derivationErrors;
private static String[] _prefixes;
static {
MultiClassLoader l = AccessController.doPrivileged(J2DoPrivHelper.newMultiClassLoaderAction());
l.addClassLoader(0, AccessController
.doPrivileged(J2DoPrivHelper.getClassLoaderAction(ProductDerivations.class)));
l.addClassLoader(1, AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction()));
_derivationNames = Services.getImplementors(ProductDerivation.class, l);
_derivationErrors = new Throwable[_derivationNames.length];
List<ProductDerivation> derivations =
new ArrayList<>(_derivationNames.length);
boolean errors = false;
for (int i = 0; i < _derivationNames.length; i++) {
try {
ProductDerivation d = (ProductDerivation)
AccessController.doPrivileged(
J2DoPrivHelper.newInstanceAction(
Class.forName(_derivationNames[i], true, l)));
d.validate();
derivations.add(d);
} catch (Throwable t) {
if (t instanceof PrivilegedActionException)
t = ((PrivilegedActionException) t).getException();
_derivationErrors[i] = t;
errors = true;
}
}
// must be at least one product derivation to define metadata factories,
// etc.
if (derivations.isEmpty()) {
throw new MissingResourceException(_loc.get
("no-product-derivations", ProductDerivation.class.getName(),
derivationErrorsToString()).getMessage(),
ProductDerivations.class.getName(),"derivations");
}
// START - ALLOW PRINT STATEMENTS
// if some derivations weren't instantiable, warn
if (errors)
System.err.println(_loc.get("bad-product-derivations",
ProductDerivations.class.getName()));
for (int i = 0; i < _derivationErrors.length; i++) {
if (_derivationErrors[i] == null)
continue;
System.err.println(_derivationNames[i] + ":" +
_derivationErrors[i]);
break;
}
// STOP - ALLOW PRINT STATEMENTS
Collections.sort(derivations, new ProductDerivationComparator());
_derivations =
derivations.toArray(new ProductDerivation[derivations.size()]);
List<String> prefixes = new ArrayList<>(2);
prefixes.add("openjpa");
for (ProductDerivation derivation : _derivations) {
String prefix = derivation.getConfigurationPrefix();
if (prefix != null && !"openjpa".equals(prefix))
prefixes.add(prefix);
}
_prefixes = prefixes.toArray(new String[prefixes.size()]);
}
/**
* Return all the product derivations registered in the current classloader
*/
public static ProductDerivation[] getProductDerivations() {
return _derivations;
}
/**
* Return the recognized prefixes for configuration properties.
*/
public static String[] getConfigurationPrefixes() {
return _prefixes;
}
/**
* Set the configuration prefix array. This is package-visible for
* testing purposes.
*
* @since 0.9.7
*/
static void setConfigurationPrefixes(String[] prefixes) {
_prefixes = prefixes;
}
/**
* Determine the full key name for <code>partialKey</code>, given the
* registered prefixes and the entries in <code>map</code>. This method
* computes the appropriate configuration prefix to use by looking
* through <code>map</code> for a key starting with any of the known
* configuration prefixes and ending with <code>partialKey</code> and, if a
* value is found, using the prefix of that key. Otherwise, it uses
* the first registered prefix.
*
* The given <code>partialKey</code> is first tested for containment in the
* given map without any prefix.
*
* @since 0.9.7
*/
public static String getConfigurationKey(String partialKey, Map map) {
String firstKey = (map != null && map.containsKey(partialKey))
? partialKey : null;
for (int i = 0; map != null && i < _prefixes.length; i++) {
String fullKey = _prefixes[i] + "." + partialKey;
if (map.containsKey(fullKey)) {
if (firstKey == null)
firstKey = fullKey;
else {
// if we've already found a property with a previous
// prefix, then this is a collision.
throw new IllegalStateException(_loc.get(
"dup-with-different-prefixes", firstKey, fullKey)
.getMessage());
}
}
}
if (firstKey == null)
return _prefixes[0] + "." + partialKey;
else
return firstKey;
}
/**
* Apply {@link ProductDerivation#beforeConfigurationConstruct} callbacks
* to the the given instance. Exceptions other than fatal
* {@link BootstrapException} are swallowed.
*/
public static void beforeConfigurationConstruct(ConfigurationProvider cp) {
for (ProductDerivation derivation : _derivations) {
try {
derivation.beforeConfigurationConstruct(cp);
}
catch (BootstrapException be) {
if (be.isFatal())
throw be;
}
catch (Exception e) {
// can't log; no configuration yet
e.printStackTrace();
}
}
}
/**
* Apply {@link ProductDerivation#beforeConfigurationLoad} callbacks
* to the the given instance. Exceptions other than fatal
* {@link BootstrapException} are swallowed.
*/
public static void beforeConfigurationLoad(Configuration conf) {
for (ProductDerivation derivation : _derivations) {
try {
derivation.beforeConfigurationLoad(conf);
}
catch (BootstrapException be) {
if (be.isFatal())
throw be;
}
catch (Exception e) {
// logging not configured yet
e.printStackTrace();
}
}
}
/**
* Apply {@link ProductDerivation#afterSpecificationSet} callbacks
* to the the given instance. Exceptions other than fatal
* {@link BootstrapException} are swallowed.
*/
public static void afterSpecificationSet(Configuration conf) {
for (ProductDerivation derivation : _derivations) {
try {
derivation.afterSpecificationSet(conf);
}
catch (BootstrapException be) {
if (be.isFatal())
throw be;
}
catch (Exception e) {
// logging not configured yet
e.printStackTrace();
}
}
}
/**
* Called as the first step of a Configuration's close() method.
* Exceptions are swallowed.
*
* @since 0.9.7
*/
public static void beforeClose(Configuration conf) {
for (ProductDerivation derivation : _derivations) {
try {
derivation.beforeConfigurationClose(conf);
}
catch (Exception e) {
conf.getConfigurationLog().warn(_loc.get("before-close-ex"), e);
}
}
}
/**
* Load the given given resource, or return false if it is not a resource
* this provider understands. The given class loader may be null.
*
* @param anchor optional named anchor within a multiple-configuration
* resource
*/
public static ConfigurationProvider load(String resource, String anchor,
ClassLoader loader) {
if (StringUtil.isEmpty(resource))
return null;
if (loader == null)
loader = AccessController.doPrivileged(
J2DoPrivHelper.getContextClassLoaderAction());
ConfigurationProvider provider = null;
StringBuilder errs = null;
// most specific to least
Throwable err = null;
for (int i = _derivations.length - 1; i >= 0; i--) {
try {
provider = _derivations[i].load(resource, anchor, loader);
if (provider != null)
return provider;
} catch (Throwable t) {
err = t;
errs = (errs == null) ? new StringBuilder() : errs.append("\n");
errs.append(_derivations[i].getClass().getName() + ":" + t);
}
}
reportErrors(errs, resource, err);
String rsrc = resource + "#" + anchor;
MissingResourceException ex = new MissingResourceException(rsrc,
ProductDerivations.class.getName(), rsrc);
ex.initCause(err);
throw ex;
}
/**
* Load given file, or return false if it is not a file this provider
* understands.
*
* @param anchor optional named anchor within a multiple-configuration file
*/
public static ConfigurationProvider load(File file, String anchor,
ClassLoader loader) {
if (file == null)
return null;
if (loader == null)
loader = AccessController.doPrivileged(
J2DoPrivHelper.getContextClassLoaderAction());
ConfigurationProvider provider = null;
StringBuilder errs = null;
Throwable err = null;
// most specific to least
for (int i = _derivations.length - 1; i >= 0; i--) {
try {
provider = _derivations[i].load(file, anchor);
if (provider != null)
return provider;
} catch (Throwable t) {
err = t;
errs = (errs == null) ? new StringBuilder() : errs.append("\n");
errs.append(_derivations[i].getClass().getName() + ":" + t);
}
}
String aPath = AccessController.doPrivileged(
J2DoPrivHelper.getAbsolutePathAction(file));
reportErrors(errs, aPath, err);
String rsrc = aPath + "#" + anchor;
MissingResourceException ex = new MissingResourceException(rsrc,
ProductDerivations.class.getName(), rsrc);
ex.initCause(err);
throw ex;
}
/**
* Return a {@link ConfigurationProvider} that has parsed system defaults.
*/
public static ConfigurationProvider loadDefaults(ClassLoader loader) {
return load(loader, false);
}
/**
* Return a {@link ConfigurationProvider} that has parsed system globals.
*/
public static ConfigurationProvider loadGlobals(ClassLoader loader) {
return load(loader, true);
}
/**
* Load a built-in resource location.
*/
private static ConfigurationProvider load(ClassLoader loader,
boolean globals) {
if (loader == null)
loader = AccessController.doPrivileged(
J2DoPrivHelper.getContextClassLoaderAction());
ConfigurationProvider provider = null;
StringBuilder errs = null;
String type = (globals) ? "globals" : "defaults";
Throwable err = null;
// most specific to least
for (int i = _derivations.length - 1; i >= 0; i--) {
try {
provider = (globals) ? _derivations[i].loadGlobals(loader)
: _derivations[i].loadDefaults(loader);
if (provider != null)
return provider;
} catch (Throwable t) {
err = t;
errs = (errs == null) ? new StringBuilder() : errs.append("\n");
errs.append(_derivations[i].getClass().getName() + ":" + t);
}
}
reportErrors(errs, type, err);
return null;
}
/**
* Thrown proper exception for given errors.
*/
private static void reportErrors(StringBuilder errs, String resource,
Throwable nested) {
if (errs == null)
return;
MissingResourceException ex = new MissingResourceException(errs.toString(),
ProductDerivations.class.getName(), resource);
ex.initCause(nested);
throw ex;
}
/**
* Return a List<String> of all the fully-qualified anchors specified in
* <code>propertiesLocation</code>. The return values must be used in
* conjunction with <code>propertiesLocation</code>. If there are no
* product derivations or if no product derivations could find anchors,
* this returns an empty list.
*
* @since 1.1.0
*/
public static List<String> getFullyQualifiedAnchorsInPropertiesLocation(
final String propertiesLocation) {
List<String> fqAnchors = new ArrayList<>();
StringBuilder errs = null;
Throwable err = null;
for (int i = _derivations.length - 1; i >= 0; i--) {
try {
if (propertiesLocation == null) {
String loc = _derivations[i].getDefaultResourceLocation();
addAll(fqAnchors, loc,
_derivations[i].getAnchorsInResource(loc));
continue;
}
File f = new File(propertiesLocation);
if ((Boolean) J2DoPrivHelper.isFileAction(f).run()) {
addAll(fqAnchors, propertiesLocation,
_derivations[i].getAnchorsInFile(f));
} else {
f = new File("META-INF" + File.separatorChar
+ propertiesLocation);
if ((Boolean) J2DoPrivHelper.isFileAction(f).run()) {
addAll(fqAnchors, propertiesLocation,
_derivations[i].getAnchorsInFile(f));
} else {
addAll(fqAnchors, propertiesLocation,
_derivations[i].getAnchorsInResource(
propertiesLocation));
}
}
} catch (Throwable t) {
err = t;
errs = (errs == null) ? new StringBuilder() : errs.append("\n");
errs.append(_derivations[i].getClass().getName() + ":" + t);
}
}
reportErrors(errs, propertiesLocation, err);
return fqAnchors;
}
private static void addAll(Collection collection, String base,
Collection newMembers) {
if (newMembers == null || collection == null)
return;
for (Object newMember : newMembers) {
String fqLoc = base + "#" + newMember;
if (!collection.contains(fqLoc))
collection.add(fqLoc);
}
}
public static Set<String> getSupportedQueryHints() {
Set<String> result = new TreeSet<>();
// most specific to least
for (int i = _derivations.length - 1; i >= 0; i--) {
Set<String> members = _derivations[i].getSupportedQueryHints();
if (members != null && !members.isEmpty())
result.addAll(members);
}
return result;
}
/**
* Compare {@link ProductDerivation}s.
*/
private static class ProductDerivationComparator
implements Comparator<ProductDerivation> {
@Override
public int compare(ProductDerivation o1, ProductDerivation o2) {
int type1 = o1.getType();
int type2 = o2.getType();
if (type1 != type2)
return type1 - type2;
// arbitrary but consistent order
return o1.getClass().getName().compareTo(o2.getClass().
getName());
}
}
/**
* Prints product derivation information.
*/
public static void main(String[] args) {
// START - ALLOW PRINT STATEMENTS
System.err.println(derivationErrorsToString());
// STOP - ALLOW PRINT STATEMENTS
}
/**
* Return a message about the status of each product derivation.
*/
private static String derivationErrorsToString() {
StringBuilder buf = new StringBuilder();
buf.append("ProductDerivations: ").append(_derivationNames.length);
for (int i = 0; i < _derivationNames.length; i++) {
buf.append("\n").append(i + 1).append(". ").
append(_derivationNames[i]).append(": ");
if (_derivationErrors[i] == null)
buf.append("OK");
else
buf.append(_derivationErrors[i].toString());
}
return buf.toString();
}
}