1 /*
2 * Copyright 2008-2019 by Emeric Vernat
3 *
4 * This file is part of Java Melody.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package net.bull.javamelody;
19
20 import java.io.Serializable;
21 import java.lang.reflect.InvocationHandler;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24
25 import net.bull.javamelody.internal.common.Parameters;
26 import net.bull.javamelody.internal.model.Counter;
27
28 /**
29 * Proxy de monitoring pour tout façade ayant une interface.
30 * Il est destiné à un compteur pour les statistiques d'exécutions de méthodes sur les "façades métiers"
31 * qui ne sont ni EJB 3 ni Spring.
32 * Il s'utilise comme ceci :
33 * {@code facade = MonitoringProxy.createProxy(facade);}
34 * @author Emeric Vernat
35 */
36 public class MonitoringProxy implements InvocationHandler, Serializable {
37 // cette classe ainsi que JdbcWrapper.DelegatingInvocationHandler sont sérialisables
38 // pour qu'un proxy soit sérialisable si la façade est sérialisable
39 private static final long serialVersionUID = 1882880665014391301L;
40
41 private static final String BEANS_ICON_NAME = "beans.png";
42 private static final Counter SERVICES_COUNTER = new Counter("services", BEANS_ICON_NAME,
43 JdbcWrapper.SINGLETON.getSqlCounter());
44 // EJB_COUNTER déclaré ici pour que l'appel dans MonitoringFilter ne déclenche pas
45 // ClassNotFoundException si les classes javax.interceptor ne sont pas présentes
46 private static final Counter EJB_COUNTER = new Counter("ejb", BEANS_ICON_NAME,
47 JdbcWrapper.SINGLETON.getSqlCounter());
48 // JPA_COUNTER déclaré ici pour que l'appel dans MonitoringFilter ne déclenche pas
49 // ClassNotFoundException si les classes javax.persistence ne sont pas présentes
50 private static final Counter JPA_COUNTER = new Counter("jpa", "db.png",
51 JdbcWrapper.SINGLETON.getSqlCounter());
52 // SPRING_COUNTER déclaré ici pour que l'appel dans MonitoringFilter ne déclenche pas
53 // ClassNotFoundException si les classes Spring et AOP alliance ne sont pas présentes
54 private static final Counter SPRING_COUNTER = new Counter("spring", BEANS_ICON_NAME,
55 JdbcWrapper.SINGLETON.getSqlCounter());
56 // GUICE_COUNTER déclaré ici pour que l'appel dans MonitoringFilter ne déclenche pas
57 // ClassNotFoundException si les classes AOP alliance ne sont pas présentes
58 private static final Counter GUICE_COUNTER = new Counter("guice", BEANS_ICON_NAME,
59 JdbcWrapper.SINGLETON.getSqlCounter());
60 // STRUTS_COUNTER déclaré ici pour que l'appel dans MonitoringFilter ne déclenche pas
61 // ClassNotFoundException si les classes xwork de Struts 2 ne sont pas présentes
62 private static final Counter STRUTS_COUNTER = new Counter(Counter.STRUTS_COUNTER_NAME,
63 "struts.png", JdbcWrapper.SINGLETON.getSqlCounter());
64 // JSF_COUNTER déclaré ici pour que l'appel dans MonitoringFilter ne déclenche pas
65 // ClassNotFoundException si les classes com.sun.faces de JSF ne sont pas présentes
66 private static final Counter JSF_COUNTER = new Counter(Counter.JSF_COUNTER_NAME, "jsp.png",
67 JdbcWrapper.SINGLETON.getSqlCounter());
68
69 private static final boolean COUNTER_HIDDEN = Parameters
70 .isCounterHidden(SERVICES_COUNTER.getName());
71 private static final boolean DISABLED = Parameter.DISABLED.getValueAsBoolean();
72 @SuppressWarnings("all")
73 private final Object facade;
74 private final String name;
75
76 /**
77 * Constructeur privé : instanciation pour méthode createProxy ci-dessous.
78 * @param facade Object
79 */
80 protected MonitoringProxy(Object facade) {
81 this(facade, null);
82 }
83
84 /**
85 * Constructeur privé : instanciation pour méthode createProxy ci-dessous.
86 * @param facade Object
87 * @param name override of the interface name in the statistics
88 */
89 protected MonitoringProxy(Object facade, String name) {
90 super();
91 this.facade = facade;
92 // quand cet intercepteur est utilisé, le compteur est affiché
93 // sauf si le paramètre displayed-counters dit le contraire
94 SERVICES_COUNTER.setDisplayed(!COUNTER_HIDDEN);
95 SERVICES_COUNTER.setUsed(true);
96 this.name = name;
97 }
98
99 /**
100 * Création d'un proxy de monitoring pour une façade.
101 * @param <T> Type de la façade (une interface en général).
102 * @param facade Instance de la façade
103 * @return Proxy de la façade
104 */
105 public static <T> T createProxy(T facade) {
106 return createProxy(facade, new MonitoringProxy(facade));
107 }
108
109 /**
110 * Création d'un proxy de monitoring pour une façade, en spécifiant le nom qui sera affiché dans le monitoring.
111 * @param <T> Type de la façade (une interface en général).
112 * @param facade Instance de la façade
113 * @param name override of the interface name in the statistics
114 * @return Proxy de la façade
115 */
116 public static <T> T createProxy(T facade, String name) {
117 return createProxy(facade, new MonitoringProxy(facade, name));
118 }
119
120 static Counter getServicesCounter() {
121 return SERVICES_COUNTER;
122 }
123
124 static Counter getEjbCounter() {
125 return EJB_COUNTER;
126 }
127
128 static Counter getJpaCounter() {
129 return JPA_COUNTER;
130 }
131
132 static Counter getSpringCounter() {
133 return SPRING_COUNTER;
134 }
135
136 static Counter getGuiceCounter() {
137 return GUICE_COUNTER;
138 }
139
140 static Counter getStrutsCounter() {
141 return STRUTS_COUNTER;
142 }
143
144 static Counter getJsfCounter() {
145 return JSF_COUNTER;
146 }
147
148 /**
149 * Intercepte une exécution de méthode sur une façade.
150 * @param proxy Object
151 * @param method Method
152 * @param args Object[]
153 * @return Object
154 * @throws Throwable t
155 */
156 @Override
157 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
158 if (DISABLED || !SERVICES_COUNTER.isDisplayed()) {
159 return method.invoke(facade, args);
160 }
161 // nom identifiant la requête
162 final String requestName = getRequestName(method);
163
164 return invokeTarget(facade, method, args, requestName);
165 }
166
167 /**
168 * Invoke target.
169 * @param target Instance to call
170 * @param method Method to call
171 * @param args Method arguments
172 * @param requestName Request name to display
173 * @return Result
174 * @throws IllegalAccessException e
175 * @throws InvocationTargetException e
176 */
177 public static Object invokeTarget(Object target, Method method, Object[] args,
178 final String requestName) throws IllegalAccessException, InvocationTargetException {
179 boolean systemError = false;
180 try {
181 SERVICES_COUNTER.bindContextIncludingCpu(requestName);
182 return method.invoke(target, args);
183 } catch (final InvocationTargetException e) {
184 if (e.getCause() instanceof Error) {
185 // on catche Error pour avoir les erreurs systèmes
186 // mais pas Exception qui sont fonctionnelles en général
187 systemError = true;
188 }
189 throw e;
190 } finally {
191 // on enregistre la requête dans les statistiques
192 SERVICES_COUNTER.addRequestForCurrentContext(systemError);
193 }
194 }
195
196 protected String getRequestName(Method method) {
197 final String requestName;
198 if (name == null) {
199 requestName = method.getDeclaringClass().getSimpleName() + '.' + method.getName();
200 } else {
201 requestName = name + '.' + method.getName();
202 }
203 return requestName;
204 }
205
206 protected String getName() {
207 return name;
208 }
209
210 protected static <T> T createProxy(T facade, MonitoringProxy monitoringProxy) {
211 return JdbcWrapper.createProxy(facade, monitoringProxy);
212 }
213 }
214