javax.persistence.spi.PersistenceProviderResolverHolder Maven / Gradle / Ivy
// $Id: PersistenceProviderResolverHolder.java 17752 2009-10-15 01:19:21Z [email protected] $
// EJB3 Specification Copyright 2004-2009 Sun Microsystems, Inc.
package javax.persistence.spi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.PersistenceException;
/**
* Holds the global PersistenceProviderResolver instance.
* If no PersistenceProviderResolver is set by the environment,
* the default PersistenceProviderResolver is used.
*
* Implementations must be thread-safe.
*/
public class PersistenceProviderResolverHolder {
private static final PersistenceProviderResolver DEFAULT_RESOLVER = new PersistenceProviderResolverPerClassLoader();
private static volatile PersistenceProviderResolver RESOLVER;
/**
* Returns the current persistence provider resolver
*
* @return persistence provider resolver in use
*/
public static PersistenceProviderResolver getPersistenceProviderResolver() {
return RESOLVER == null ? DEFAULT_RESOLVER : RESOLVER;
}
/**
* Defines the persistence provider resolver used.
*
* @param resolver PersistenceProviderResolver to be used.
*/
public static void setPersistenceProviderResolver(PersistenceProviderResolver resolver) {
RESOLVER = resolver;
}
/**
* Cache PersistenceProviderResolver per classloader and use the current classloader as a
* key.
* Use CachingPersistenceProviderResolver for each PersistenceProviderResolver instance.
*
* @author Emmanuel Bernard
*/
private static class PersistenceProviderResolverPerClassLoader implements PersistenceProviderResolver {
//FIXME use a ConcurrentHashMap with weak entry
private final WeakHashMap resolvers =
new WeakHashMap();
private volatile short barrier = 1;
/**
* {@inheritDoc}
*/
public List getPersistenceProviders() {
ClassLoader cl = getContextualClassLoader();
if ( barrier == 1 ) {} //read barrier syncs state with other threads
PersistenceProviderResolver currentResolver = resolvers.get( cl );
if ( currentResolver == null ) {
currentResolver = new CachingPersistenceProviderResolver( cl );
resolvers.put( cl, currentResolver );
barrier = 1;
}
return currentResolver.getPersistenceProviders();
}
/**
* {@inheritDoc}
*/
public void clearCachedProviders() {
// todo : should we clear all providers from all resolvers here?
ClassLoader cl = getContextualClassLoader();
if ( barrier == 1 ) {} //read barrier syncs state with other threads
PersistenceProviderResolver currentResolver = resolvers.get( cl );
if ( currentResolver != null ) {
currentResolver.clearCachedProviders();
}
}
private static ClassLoader getContextualClassLoader() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if ( cl == null ) {
cl = PersistenceProviderResolverPerClassLoader.class.getClassLoader();
}
return cl;
}
/**
* Resolve the list of Persistence providers for a given classloader and cache the results.
*
* Avoids to keep any reference from this class to the classloader being
* passed to the constructor.
*
* @author Emmanuel Bernard
*/
private static class CachingPersistenceProviderResolver implements PersistenceProviderResolver {
//this assumes that the class loader keeps the list of classes loaded
private final List>> resolverClasses
= new ArrayList>>();
public CachingPersistenceProviderResolver(ClassLoader cl) {
loadResolverClasses( cl );
}
private void loadResolverClasses(ClassLoader cl) {
synchronized ( resolverClasses ) {
try {
Enumeration resources = cl.getResources( "META-INF/services/" + PersistenceProvider.class.getName() );
Set names = new HashSet();
while ( resources.hasMoreElements() ) {
URL url = resources.nextElement();
InputStream is = url.openStream();
try {
names.addAll( providerNamesFromReader( new BufferedReader( new InputStreamReader( is ) ) ) );
}
finally {
is.close();
}
}
for ( String s : names ) {
@SuppressWarnings( "unchecked" )
Class extends PersistenceProvider> providerClass = (Class extends PersistenceProvider>) cl.loadClass( s );
WeakReference> reference
= new WeakReference>(providerClass);
//keep Hibernate atop
if ( s.endsWith( "HibernatePersistence" ) && resolverClasses.size() > 0 ) {
WeakReference> movedReference = resolverClasses.get( 0 );
resolverClasses.add( 0, reference );
resolverClasses.add( movedReference );
}
else {
resolverClasses.add( reference );
}
}
}
catch ( IOException e ) {
throw new PersistenceException( e );
}
catch ( ClassNotFoundException e ) {
throw new PersistenceException( e );
}
}
}
/**
* {@inheritDoc}
*/
public List getPersistenceProviders() {
//TODO find a way to cache property instances
//problem #1: avoid hard ref with classloader (List>?
//problem #2: avoid half GC lists
// todo (steve) : why arent we just caching the PersistenceProvider *instances* as the CachingPersistenceProviderResolver state???
synchronized ( resolverClasses ) {
List providers = new ArrayList( resolverClasses.size() );
try {
for ( WeakReference> providerClass : resolverClasses ) {
providers.add( providerClass.get().newInstance() );
}
}
catch ( InstantiationException e ) {
throw new PersistenceException( e );
}
catch ( IllegalAccessException e ) {
throw new PersistenceException( e );
}
return providers;
}
}
/**
* {@inheritDoc}
*/
public synchronized void clearCachedProviders() {
synchronized ( resolverClasses ) {
resolverClasses.clear();
loadResolverClasses( PersistenceProviderResolverPerClassLoader.getContextualClassLoader() );
}
}
private static final Pattern nonCommentPattern = Pattern.compile( "^([^#]+)" );
private static Set providerNamesFromReader(BufferedReader reader) throws IOException {
Set names = new HashSet();
String line;
while ( ( line = reader.readLine() ) != null ) {
line = line.trim();
Matcher m = nonCommentPattern.matcher( line );
if ( m.find() ) {
names.add( m.group().trim() );
}
}
return names;
}
}
}
}