1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package net.bull.javamelody;
18
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.Method;
21 import java.security.AccessController;
22 import java.security.PrivilegedAction;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import javax.persistence.EntityManagerFactory;
29 import javax.persistence.spi.LoadState;
30 import javax.persistence.spi.PersistenceProvider;
31 import javax.persistence.spi.PersistenceProviderResolver;
32 import javax.persistence.spi.PersistenceProviderResolverHolder;
33 import javax.persistence.spi.PersistenceUnitInfo;
34 import javax.persistence.spi.ProviderUtil;
35
36 import net.bull.javamelody.internal.common.LOG;
37 import net.bull.javamelody.internal.common.Parameters;
38 import net.bull.javamelody.internal.model.Counter;
39
40 /**
41  * Persistence provider pour monitorer JPA.
42  * From Sirona, http://sirona.incubator.apache.org/
43  */

44 public class JpaPersistence implements PersistenceProvider {
45     private static final Counter JPA_COUNTER = MonitoringProxy.getJpaCounter();
46     private static final boolean COUNTER_HIDDEN = Parameters.isCounterHidden(JPA_COUNTER.getName());
47
48     /**
49      * The name of the {@link javax.persistence.spi.PersistenceProvider} implementor
50      * <p/>
51      * See JPA 2 sections 9.4.3 and 8.2.1.4
52      */

53     private static final String JPA_PERSISTENCE_PROVIDER = "javax.persistence.provider";
54     private static final String OWN_PACKAGE = JpaPersistence.class.getName().substring(0,
55             JpaPersistence.class.getName().lastIndexOf('.'));
56     private static final String DELEGATE_PROVIDER_KEY = OWN_PACKAGE + ".jpa.provider";
57
58     private static final String[] PROVIDERS = {
59             "org.apache.openjpa.persistence.PersistenceProviderImpl",
60             "org.hibernate.jpa.HibernatePersistenceProvider",
61             "org.hibernate.ejb.HibernatePersistence",
62             "org.eclipse.persistence.jpa.PersistenceProvider",
63             "oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider",
64             "oracle.toplink.essentials.PersistenceProvider",
65             "me.prettyprint.hom.CassandraPersistenceProvider",
66             "org.datanucleus.jpa.PersistenceProviderImpl",
67             "com.orientechnologies.orient.core.db.object.jpa.OJPAPersistenceProvider",
68             "com.orientechnologies.orient.object.jpa.OJPAPersistenceProvider",
69             "com.spaceprogram.simplejpa.PersistenceProviderImpl", };
70
71     private static final ProviderUtil DUMMY_PROVIDER_UTIL = new ProviderUtil() {
72         @Override
73         public LoadState isLoadedWithoutReference(Object entity, String attributeName) {
74             return LoadState.UNKNOWN;
75         }
76
77         @Override
78         public LoadState isLoadedWithReference(Object entity, String attributeName) {
79             return LoadState.UNKNOWN;
80         }
81
82         @Override
83         public LoadState isLoaded(Object entity) {
84             return LoadState.UNKNOWN;
85         }
86     };
87
88     private volatile PersistenceProvider delegate; // NOPMD
89
90     private static class JavaMelodyPersistenceProviderResolver
91             implements PersistenceProviderResolver {
92         private final PersistenceProviderResolver delegate;
93
94         JavaMelodyPersistenceProviderResolver(PersistenceProviderResolver delegate) {
95             super();
96             this.delegate = delegate;
97         }
98
99         @Override
100         public List<PersistenceProvider> getPersistenceProviders() {
101             // avant de retourner la liste des persistence providers
102             // on met notre JpaPersistence en premier pour qu'il soit toujours choisi
103             // et qu'il délègue au persistence provider final
104             final List<PersistenceProvider> providers = delegate.getPersistenceProviders();
105             final List<PersistenceProvider> result = new ArrayList<>();
106             for (final PersistenceProvider provider : providers) {
107                 if (provider instanceof JpaPersistence) {
108                     result.add(0, provider);
109                 } else {
110                     result.add(provider);
111                 }
112             }
113             return result;
114         }
115
116         @Override
117         public void clearCachedProviders() {
118             delegate.clearCachedProviders();
119         }
120     }
121
122     /**
123      * Active le monitoring JPA par défaut,
124      * même si <provider>net.bull.javamelody.JpaPersistence</provider> n'est pas dans META-INF/persistence.xml
125      */

126     public static void initPersistenceProviderResolver() {
127         try {
128             PersistenceProviderResolver resolver = PersistenceProviderResolverHolder
129                     .getPersistenceProviderResolver();
130             if (!(resolver instanceof JavaMelodyPersistenceProviderResolver)) {
131                 resolver = new JavaMelodyPersistenceProviderResolver(resolver);
132                 PersistenceProviderResolverHolder.setPersistenceProviderResolver(resolver);
133                 LOG.debug("JPA persistence provider resolver initialized");
134             }
135         } catch (final Throwable t) { // NOPMD
136             LOG.info("initialization of jpa persistence provider resolver failed, skipping");
137         }
138     }
139
140     // cette classe est instanciée dès le démarrage (WildFly notamment),
141     // il ne faut donc pas appeler initJpaCounter() dans le constructeur
142
143     private void initJpaCounter() {
144         // quand cette classe est utilisée, le compteur est affiché
145         // sauf si le paramètre displayed-counters dit le contraire
146         JPA_COUNTER.setDisplayed(!COUNTER_HIDDEN);
147         // setUsed(true) nécessaire ici si le contexte jpa est initialisé avant FilterContext
148         // sinon les statistiques jpa ne sont pas affichées
149         JPA_COUNTER.setUsed(true);
150         LOG.debug("jpa persistence initialized");
151     }
152
153     /** {@inheritDoc} */
154     @SuppressWarnings({ "rawtypes""unchecked" })
155     @Override
156     public EntityManagerFactory createEntityManagerFactory(final String unit, final Map map) {
157         initJpaCounter();
158         final PersistenceProvider persistenceProvider = findDelegate(map);
159         final ClassLoader tccl = tccl();
160
161         final ClassLoader hack = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { // pour findbugs
162             /** {@inheritDoc} */
163             @Override
164             public ClassLoader run() {
165                 return new JpaOverridePersistenceXmlClassLoader(tccl,
166                         persistenceProvider.getClass().getName());
167             }
168         });
169
170         Thread.currentThread().setContextClassLoader(hack);
171         try {
172             final Map overridenMap = new HashMap();
173             if (map != null) {
174                 overridenMap.putAll(map);
175             }
176             // #869 No Persistence provider for EntityManager, with Hibernate 5.4 & JPA
177             // (when JpaOverridePersistenceXmlClassLoader is not enough)
178             overridenMap.put(JPA_PERSISTENCE_PROVIDER, persistenceProvider.getClass().getName());
179             final EntityManagerFactory entityManagerFactory = persistenceProvider
180                     .createEntityManagerFactory(unit, overridenMap);
181             if (entityManagerFactory == null) {
182                 return null;
183             }
184             return JpaWrapper.createEntityManagerFactoryProxy(entityManagerFactory);
185         } finally {
186             Thread.currentThread().setContextClassLoader(tccl);
187         }
188     }
189
190     /** {@inheritDoc} */
191     @SuppressWarnings({ "rawtypes""unchecked" })
192     @Override
193     public EntityManagerFactory createContainerEntityManagerFactory(final PersistenceUnitInfo info,
194             final Map map) {
195         initJpaCounter();
196         final PersistenceProvider persistenceProvider = findDelegate(map);
197         // on surcharge PersistenceUnitInfo.getPersistenceProviderClassName()
198         // pour retourner le PersistenceProvider délégué et pas nous même
199         final PersistenceUnitInfo proxiedInfo = createPersistentUnitInfoProxy(info,
200                 persistenceProvider);
201         final Map overridenMap = new HashMap();
202         if (map != null) {
203             overridenMap.putAll(map);
204         }
205         // #869 No Persistence provider for EntityManager, with Hibernate 5.4 & JPA
206         // (when JpaOverridePersistenceXmlClassLoader is not enough)
207         overridenMap.put(JPA_PERSISTENCE_PROVIDER, persistenceProvider.getClass().getName());
208         final EntityManagerFactory entityManagerFactory = persistenceProvider
209                 .createContainerEntityManagerFactory(proxiedInfo, overridenMap);
210         if (entityManagerFactory == null) {
211             return null;
212         }
213         return JpaWrapper.createEntityManagerFactoryProxy(entityManagerFactory);
214     }
215
216     private PersistenceUnitInfo createPersistentUnitInfoProxy(final PersistenceUnitInfo info,
217             final PersistenceProvider persistenceProvider) {
218         final InvocationHandler invocationHandler = new ProviderAwareHandler(
219                 persistenceProvider.getClass().getName(), info);
220         return JdbcWrapper.createProxy(info, invocationHandler);
221     }
222
223     /** {@inheritDoc} */
224     @Override
225     public ProviderUtil getProviderUtil() { // we suppose it is loaded later than createXXXEMF so we'll get the delegate
226         if (delegate == null) {
227             // delegate not yet loaded and perhaps will never be:
228             // this method may be called, even without jpa impl to delegate,
229             // for example, by hibernate validator via jpa api but without jpa impl
230             // (issue 396, if loadOrGuessDelegate(null) was called here),
231             // so return a dumb ProviderUtil in this case or if delegate not yet loaded
232             return DUMMY_PROVIDER_UTIL;
233         }
234         return delegate.getProviderUtil();
235     }
236
237     private PersistenceProvider findDelegate(final Map<?, ?> map) {
238         if (map == null) {
239             return loadOrGuessDelegate(null);
240         }
241         return loadOrGuessDelegate(String.class.cast(map.get(DELEGATE_PROVIDER_KEY)));
242     }
243
244     private PersistenceProvider loadOrGuessDelegate(final String name) {
245         if (delegate == null) {
246             synchronized (this) {
247                 if (delegate == null) {
248                     if (name == null) {
249                         guessDelegate();
250                     } else {
251                         try {
252                             delegate = newPersistence(name);
253                         } catch (final Exception e) {
254                             throw new IllegalStateException(new ClassNotFoundException(
255                                     "Can't instantiate '" + name + "'", e));
256                         }
257                     }
258                 }
259             }
260         }
261         if (name != null && !delegate.getClass().getName().equals(name)) {
262             try {
263                 return newPersistence(name);
264             } catch (final Exception e) {
265                 throw new IllegalStateException(
266                         new ClassNotFoundException("Can't instantiate '" + name + "'", e));
267             }
268         }
269         return delegate;
270     }
271
272     private void guessDelegate() {
273         // https://issues.apache.org/jira/browse/SIRONA-44
274         // https://github.com/javamelody/javamelody/issues/460
275         final List<PersistenceProvider> persistenceProviders = PersistenceProviderResolverHolder
276                 .getPersistenceProviderResolver().getPersistenceProviders();
277         for (final PersistenceProvider persistenceProvider : persistenceProviders) {
278             if (!getClass().isInstance(persistenceProvider)) {
279                 delegate = persistenceProvider;
280                 break;
281             }
282         }
283         if (delegate == null) {
284             for (final String provider : PROVIDERS) {
285                 try {
286                     delegate = newPersistence(provider);
287                     break;
288                 } catch (final Throwable th2) { // NOPMD
289                     continue;
290                 }
291             }
292             if (delegate == null) {
293                 throw new IllegalStateException(
294                         new ClassNotFoundException("Can't find a delegate"));
295             }
296         }
297     }
298
299     private static ClassLoader tccl() {
300         return Thread.currentThread().getContextClassLoader();
301     }
302
303     private static PersistenceProvider newPersistence(final String name) throws Exception { // NOPMD
304         return PersistenceProvider.class.cast(tccl().loadClass(name).newInstance());
305     }
306
307     private static class ProviderAwareHandler implements InvocationHandler {
308         private final String provider;
309         private final PersistenceUnitInfo info;
310
311         ProviderAwareHandler(final String provider, final PersistenceUnitInfo info) {
312             super();
313             this.provider = provider;
314             this.info = info;
315         }
316
317         /** {@inheritDoc} */
318         @Override
319         public Object invoke(final Object proxy, final Method method, final Object[] args)
320                 throws Throwable {
321             if ("getPersistenceProviderClassName".equals(method.getName())) {
322                 return provider;
323             }
324             return method.invoke(info, args);
325         }
326     }
327
328     /** {@inheritDoc} */
329     @SuppressWarnings("rawtypes")
330     @Override
331     public void generateSchema(final PersistenceUnitInfo info, final Map map) {
332         final PersistenceProvider persistenceProvider = findDelegate(map);
333         persistenceProvider.generateSchema(info, map);
334     }
335
336     /** {@inheritDoc} */
337     @SuppressWarnings("rawtypes")
338     @Override
339     public boolean generateSchema(final String persistenceUnitName, final Map map) {
340         final PersistenceProvider persistenceProvider = findDelegate(map);
341         return persistenceProvider.generateSchema(persistenceUnitName, map);
342     }
343 }
344