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