1 /*
2  * Copyright (c) 2008, 2019 Oracle and/or its affiliates. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v. 2.0 which is available at
6  * http://www.eclipse.org/legal/epl-2.0,
7  * or the Eclipse Distribution License v. 1.0 which is available at
8  * http://www.eclipse.org/org/documents/edl-v10.php.
9  *
10  * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
11  */

12
13 // Contributors:
14 //     Lukas Jungmann  - 2.2
15 //     Linda DeMichiel - 2.1
16 //     Linda DeMichiel - 2.0
17
18 package javax.persistence.spi;
19
20 import java.lang.ref.ReferenceQueue;
21 import java.lang.ref.SoftReference;
22 import java.lang.ref.WeakReference;
23 import java.security.AccessController;
24 import java.security.PrivilegedAction;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.ServiceConfigurationError;
30 import java.util.ServiceLoader;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33
34
35 /**
36  * Holds the global {@link javax.persistence.spi.PersistenceProviderResolver}
37  * instance. If no <code>PersistenceProviderResolver</code> is set by the
38  * environment, the default <code>PersistenceProviderResolver</code> is used.
39  * 
40  * Implementations must be thread-safe.
41  * 
42  * @since 2.0
43  */

44 public class PersistenceProviderResolverHolder {
45
46     private static PersistenceProviderResolver singleton = new DefaultPersistenceProviderResolver();
47
48     /**
49      * Returns the current persistence provider resolver.
50      * 
51      * @return the current persistence provider resolver
52      */

53     public static PersistenceProviderResolver getPersistenceProviderResolver() {
54         return singleton;
55     }
56
57     /**
58      * Defines the persistence provider resolver used.
59      * 
60      * @param resolver persistence provider resolver to be used.
61      */

62     public static void setPersistenceProviderResolver(PersistenceProviderResolver resolver) {
63         if (resolver == null) {
64             singleton = new DefaultPersistenceProviderResolver();
65         } else {
66             singleton = resolver;
67         }
68     }
69
70     /**
71      * Default provider resolver class to use when none is explicitly set.
72      * 
73      * Uses service loading mechanism as described in the Jakarta Persistence
74      * specification. A ServiceLoader.load() call is made with the current context
75      * classloader to find the service provider files on the classpath.
76      */

77     private static class DefaultPersistenceProviderResolver implements PersistenceProviderResolver {
78
79         /**
80          * Cached list of available providers cached by CacheKey to ensure
81          * there is not potential for provider visibility issues. 
82          */

83         private volatile HashMap<CacheKey, PersistenceProviderReference> providers = new HashMap<CacheKey, PersistenceProviderReference>();
84         
85         /**
86          * Queue for reference objects referring to class loaders or persistence providers.
87          */

88         private static final ReferenceQueue referenceQueue = new ReferenceQueue();
89
90         public List<PersistenceProvider> getPersistenceProviders() {
91             // Before we do the real loading work, see whether we need to
92             // do some cleanup: If references to class loaders or
93             // persistence providers have been nulled out, remove all related
94             // information from the cache.
95             processQueue();
96             
97             ClassLoader loader = getContextClassLoader();
98             CacheKey cacheKey = new CacheKey(loader);
99             PersistenceProviderReference providersReferent = this.providers.get(cacheKey);
100             List<PersistenceProvider> loadedProviders = null;
101             
102             if (providersReferent != null) {
103                 loadedProviders = providersReferent.get();
104             }
105
106             if (loadedProviders == null) {
107                 loadedProviders = new ArrayList<>();
108                 Iterator<PersistenceProvider> ipp = ServiceLoader.load(PersistenceProvider.class, loader).iterator();
109                 try {
110                     while (ipp.hasNext()) {
111                         try {
112                             PersistenceProvider pp = ipp.next();
113                             loadedProviders.add(pp);
114                         } catch (ServiceConfigurationError sce) {
115                             log(Level.FINEST, sce.toString());
116                         }
117                     }
118                 } catch (ServiceConfigurationError sce) {
119                     log(Level.FINEST, sce.toString());
120                 }
121
122                 // If none are found we'll log the provider names for diagnostic
123                 // purposes.
124                 if (loadedProviders.isEmpty()) {
125                     log(Level.WARNING, "No valid providers found.");
126                 }
127                 
128                 providersReferent = new PersistenceProviderReference(loadedProviders, referenceQueue, cacheKey);
129
130                 this.providers.put(cacheKey, providersReferent);
131             }
132
133             return loadedProviders;
134         }
135         
136         /**
137          * Remove garbage collected cache keys & providers.
138          */

139         private void processQueue() {
140             CacheKeyReference ref;
141             while ((ref = (CacheKeyReference) referenceQueue.poll()) != null) {
142                 providers.remove(ref.getCacheKey());
143             }            
144         }
145
146         /**
147          * Wraps <code>Thread.currentThread().getContextClassLoader()</code> into a doPrivileged block if security manager is present
148          */

149         private static ClassLoader getContextClassLoader() {
150             if (System.getSecurityManager() == null) {
151                 return Thread.currentThread().getContextClassLoader();
152             } else {
153                 return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
154                     public ClassLoader run() {
155                         return Thread.currentThread().getContextClassLoader();
156                     }
157                 });
158             }
159         }
160
161
162         private static final String LOGGER_SUBSYSTEM = "javax.persistence.spi";
163
164         private Logger logger;
165
166         private void log(Level level, String message) {
167             if (this.logger == null) {
168                 this.logger = Logger.getLogger(LOGGER_SUBSYSTEM);
169             }
170             this.logger.log(level, LOGGER_SUBSYSTEM + "::" + message);
171         }
172
173         /**
174          * Clear all cached providers
175          */

176         public void clearCachedProviders() {
177             this.providers.clear();
178         }
179
180         
181         /**
182          * The common interface to get a CacheKey implemented by
183          * LoaderReference and PersistenceProviderReference.
184          */

185         private interface CacheKeyReference {
186             public CacheKey getCacheKey();
187         }        
188         
189         /**
190           * Key used for cached persistence providers. The key checks
191           * the class loader to determine if the persistence providers
192           * is a match to the requested one. The loader may be null.
193           */

194         private class CacheKey implements Cloneable {
195             
196             /* Weak Reference to ClassLoader */
197             private LoaderReference loaderRef;
198             
199             /* Cached Hashcode */
200             private int hashCodeCache;
201
202             CacheKey(ClassLoader loader) {
203                 if (loader == null) {
204                     this.loaderRef = null;
205                 } else {
206                     loaderRef = new LoaderReference(loader, referenceQueue, this);
207                 }
208                 calculateHashCode();
209             }
210
211             ClassLoader getLoader() {
212                 return (loaderRef != null) ? loaderRef.get() : null;
213             }
214
215             public boolean equals(Object other) {
216                 if (this == other) {
217                     return true;
218                 }
219                 try {
220                     final CacheKey otherEntry = (CacheKey) other;
221                     // quick check to see if they are not equal
222                     if (hashCodeCache != otherEntry.hashCodeCache) {
223                         return false;
224                     }
225                     // are refs (both non-null) or (both null)?
226                     if (loaderRef == null) {
227                         return otherEntry.loaderRef == null;
228                     }
229                     ClassLoader loader = loaderRef.get();
230                     return (otherEntry.loaderRef != null)
231                     // with a null reference we can no longer find
232                     // out which class loader was referenced; so
233                     // treat it as unequal
234                     && (loader != null) && (loader == otherEntry.loaderRef.get());
235                 } catch (NullPointerException e) {
236                 } catch (ClassCastException e) {
237                 }
238
239                 return false;
240             }
241
242             public int hashCode() {
243                 return hashCodeCache;
244             }
245
246             private void calculateHashCode() {
247                 ClassLoader loader = getLoader();
248                 if (loader != null) {
249                     hashCodeCache = loader.hashCode();
250                 }
251             }
252
253             public Object clone() {
254                 try {
255                     CacheKey clone = (CacheKey) super.clone();
256                     if (loaderRef != null) {
257                         clone.loaderRef = new LoaderReference(loaderRef.get(), referenceQueue, clone);
258                     }
259                     return clone;
260                 } catch (CloneNotSupportedException e) {
261                     // this should never happen
262                     throw new InternalError();
263                 }
264             }
265
266             public String toString() {
267                 return "CacheKey[" + getLoader() + ")]";
268             }
269         }
270        
271        /**
272          * References to class loaders are weak references, so that they can be
273          * garbage collected when nobody else is using them. The DefaultPersistenceProviderResolver 
274          * class has no reason to keep class loaders alive.
275          */

276         private class LoaderReference extends WeakReference<ClassLoader> 
277                 implements CacheKeyReference {
278             private CacheKey cacheKey;
279
280             @SuppressWarnings("unchecked")
281             LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) {
282                 super(referent, q);
283                 cacheKey = key;
284             }
285
286             public CacheKey getCacheKey() {
287                 return cacheKey;
288             }
289         }
290
291         /**
292          * References to persistence provider are soft references so that they can be garbage
293          * collected when they have no hard references.
294          */

295         private class PersistenceProviderReference extends SoftReference<List<PersistenceProvider>>
296                 implements CacheKeyReference {
297             private CacheKey cacheKey;
298
299             @SuppressWarnings("unchecked")
300             PersistenceProviderReference(List<PersistenceProvider> referent, ReferenceQueue q, CacheKey key) {
301                 super(referent, q);
302                 cacheKey = key;
303             }
304
305             public CacheKey getCacheKey() {
306                 return cacheKey;
307             }
308         }
309     }
310 }
311