/*
* Copyright 2008-2014 by Emeric Vernat
*
* This file is part of Java Melody.
*
* Licensed 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 net.bull.javamelody;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
/**
* Cette classe est utile pour construire des proxy pour JPA.
* @author Emeric Vernat
*/
final class JpaWrapper {
private static final boolean DISABLED = Boolean.parseBoolean(Parameters
.getParameter(Parameter.DISABLED));
private static final Counter JPA_COUNTER = MonitoringProxy.getJpaCounter();
private JpaWrapper() {
super();
}
static Counter getJpaCounter() {
return JPA_COUNTER;
}
static EntityManagerFactory createEntityManagerFactoryProxy(
final EntityManagerFactory entityManagerFactory) {
if (DISABLED || !JPA_COUNTER.isDisplayed()) {
return entityManagerFactory;
}
// on veut monitorer seulement les EntityManager retournés par les deux méthodes createEntityManager
return JdbcWrapper.createProxy(entityManagerFactory, new EntityManagerFactoryHandler(
entityManagerFactory));
}
static EntityManager createEntityManagerProxy(final EntityManager entityManager) {
if (DISABLED || !JPA_COUNTER.isDisplayed()) {
return entityManager;
}
return JdbcWrapper.createProxy(entityManager, new EntityManagerHandler(entityManager));
}
static Query createQueryProxy(final Query query, final String requestName) {
return JdbcWrapper.createProxy(query, new QueryHandler(query, requestName));
}
static Object doInvoke(final Object object, final Method method, final Object[] args,
final String requestName) throws Throwable {
boolean systemError = false;
try {
JPA_COUNTER.bindContextIncludingCpu(requestName);
return method.invoke(object, args);
} catch (final Error e) {
// on catche Error pour avoir les erreurs systèmes
// mais pas Exception qui sont fonctionnelles en général
systemError = true;
throw e;
} finally {
// on enregistre la requête dans les statistiques
JPA_COUNTER.addRequestForCurrentContext(systemError);
}
}
private static class EntityManagerFactoryHandler implements InvocationHandler {
private final EntityManagerFactory entityManagerFactory;
EntityManagerFactoryHandler(final EntityManagerFactory entityManagerFactory) {
super();
this.entityManagerFactory = entityManagerFactory;
}
/** {@inheritDoc} */
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
Object result = method.invoke(entityManagerFactory, args);
if (result instanceof EntityManager) {
result = createEntityManagerProxy((EntityManager) result);
}
return result;
}
}
private static class EntityManagerHandler implements InvocationHandler {
private final EntityManager entityManager;
EntityManagerHandler(final EntityManager entityManager) {
super();
this.entityManager = entityManager;
}
/** {@inheritDoc} */
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
final String methodName = method.getName();
if (isCreateSomeQuery(method)) {
Query query = (Query) method.invoke(entityManager, args);
// (pas besoin de proxy pour getCriteriaBuilder() car cela repasse par entityManager.createQuery(criteriaQuery)
final String requestName = getQueryRequestName(methodName, args);
query = createQueryProxy(query, requestName);
return query;
} else if (isOneOfFindMethods(args, methodName)) {
final String requestName = "find(" + ((Class<?>) args[0]).getSimpleName() + ')';
return doInvoke(entityManager, method, args, requestName);
} else if (isMergePersistRefreshRemoveDetachOrLockMethod(methodName, args)) {
final String requestName = method.getName() + '('
+ args[0].getClass().getSimpleName() + ')';
return doInvoke(entityManager, method, args, requestName);
} else if ("flush".equals(methodName)) {
final String requestName = "flush()";
return doInvoke(entityManager, method, args, requestName);
}
return method.invoke(entityManager, args);
}
private boolean isMergePersistRefreshRemoveDetachOrLockMethod(String methodName,
Object[] args) {
return args != null
&& args.length > 0
&& ("merge".equals(methodName) || "persist".equals(methodName)
|| "refresh".equals(methodName) || "remove".equals(methodName)
|| "detach".equals(methodName) || "lock".equals(methodName));
}
private boolean isOneOfFindMethods(Object[] args, String methodName) {
return "find".equals(methodName) && args != null && args.length > 0
&& args[0] instanceof Class;
}
private boolean isCreateSomeQuery(Method method) {
final String methodName = method.getName();
final Class<?> returnType = method.getReturnType();
final boolean methodNameOk = "createQuery".equals(methodName)
|| "createNamedQuery".equals(methodName)
|| "createNativeQuery".equals(methodName)
// JavaEE 7:
|| "createStoredProcedureQuery".equals(methodName)
|| "createNamedStoredProcedureQuery".equals(methodName);
return methodNameOk && returnType != null && Query.class.isAssignableFrom(returnType);
}
private String getQueryRequestName(String methodName, Object[] args) {
final StringBuilder requestName = new StringBuilder();
requestName.append(methodName, "create".length(), methodName.length());
appendArgs(requestName, args);
return requestName.toString();
}
private static void appendArgs(StringBuilder requestName, Object[] args) {
requestName.append('(');
if (args != null) {
String separator = "";
for (final Object arg : args) {
requestName.append(separator);
separator = ", ";
if (arg instanceof Class) {
requestName.append(((Class<?>) arg).getSimpleName());
} else {
requestName.append(arg);
}
}
}
requestName.append(')');
}
}
private static class QueryHandler implements InvocationHandler {
private final Query query;
private final String requestName;
QueryHandler(final Query query, final String requestName) {
super();
this.query = query;
this.requestName = requestName;
}
/** {@inheritDoc} */
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
final String methodName = method.getName();
if (("getSingleResult".equals(methodName) || "getResultList".equals(methodName) || "executeUpdate"
.equals(methodName)) && (args == null || args.length == 0)) {
return doInvoke(query, method, args, requestName);
} else if (methodName.startsWith("set") && method.getReturnType() != null
&& Query.class.isAssignableFrom(method.getReturnType())) {
method.invoke(query, args);
// on ne récupère pas la query en résultat, car ce doit être la même qu'en entrée.
// donc on retourne le proxy
return proxy;
}
return method.invoke(query, args);
}
}
}