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.internal.model;
19
20 import java.io.IOException;
21 import java.text.SimpleDateFormat;
22 import java.util.Calendar;
23 import java.util.Date;
24 import java.util.Locale;
25
26 import net.bull.javamelody.internal.common.LOG;
27
28 /**
29  * Factory pour les compteurs par jour, par semaine, par mois et par année.
30  * @author Emeric Vernat
31  */

32 class PeriodCounterFactory {
33     // Note d'implémentation : Calendar.getInstance() crée à chaque appel une nouvelle instance
34     // de Calendar à la date et à l'heure courante (cette date-heure peut être modifiée)
35
36     private final Counter currentDayCounter;
37
38     PeriodCounterFactory(Counter currentDayCounter) {
39         super();
40         assert currentDayCounter != null;
41         this.currentDayCounter = currentDayCounter;
42     }
43
44     Counter buildNewDayCounter() throws IOException {
45         final Calendar start = Calendar.getInstance();
46         start.setTime(currentDayCounter.getStartDate());
47         if (start.get(Calendar.MONTH) != Calendar.getInstance().get(Calendar.MONTH)) {
48             // le mois a changé, on crée un compteur vide qui sera enregistré dans un nouveau fichier;
49             // ce compteur agrégé pour le mois est utilisé pour de meilleurs performances sur le compteur de l'année
50             // on calcule le monthCounter et on l'enregistre (optimisation pour getYearCounter)
51             getMonthCounterAtDate(currentDayCounter.getStartDate());
52         }
53
54         return createDayCounterAtDate(new Date());
55     }
56
57     // compteur d'un jour donné
58     private Counter getDayCounterAtDate(Date day) {
59         final Counter dayCounter = createDayCounterAtDate(day);
60         try {
61             dayCounter.readFromFile();
62         } catch (final IOException e) {
63             // lecture échouée, tant pis
64             // (on n'interrompt pas tout un rapport juste pour un des fichiers illisible)
65             LOG.info("read of a counter file failed: " + dayCounter.getName(), e);
66         }
67         return dayCounter;
68     }
69
70     // compteur custom
71     Counter getCustomCounter(Range range) {
72         assert range.getPeriod() == null;
73         final Counter customCounter = createPeriodCounter("yyyy-MM-dd", range.getStartDate());
74         addRequestsAndErrorsForRange(customCounter, range);
75         return customCounter;
76     }
77
78     // compteur du jour courant
79     Counter getDayCounter() {
80         return currentDayCounter;
81     }
82
83     // compteur des 7 derniers jours
84     Counter getWeekCounter() {
85         final Counter weekCounter = createPeriodCounter("yyyyWW", currentDayCounter.getStartDate());
86         addRequestsAndErrorsForRange(weekCounter, Period.SEMAINE.getRange());
87         return weekCounter;
88     }
89
90     // compteur des 31 derniers jours,
91     // ici c'est un mois flottant (ie une durée), et pas un mois entier
92     Counter getMonthCounter() {
93         final Counter monthCounter = createMonthCounterAtDate(currentDayCounter.getStartDate());
94         addRequestsAndErrorsForRange(monthCounter, Period.MOIS.getRange());
95         return monthCounter;
96     }
97
98     private void addRequestsAndErrorsForRange(Counter counter, Range range) {
99         final Calendar dayCalendar = Calendar.getInstance();
100         if (range.getPeriod() == null) {
101             counter.addRequestsAndErrors(getDayCounterAtDate(range.getEndDate()));
102             dayCalendar.setTime(range.getEndDate());
103             // issue 122: attention endDate contient un jour jusqu'à 23h59m59s selon Range.parse
104             dayCalendar.set(Calendar.HOUR_OF_DAY, 0);
105             dayCalendar.set(Calendar.MINUTE, 0);
106             dayCalendar.set(Calendar.SECOND, 0);
107         } else {
108             counter.addRequestsAndErrors(currentDayCounter);
109             dayCalendar.setTime(currentDayCounter.getStartDate());
110         }
111         final int durationDays = range.getDurationDays();
112         for (int i = 1; i < durationDays; i++) {
113             // TODO optimisation avec getMonthCounterAtDate comme getYearCounter() ?
114             dayCalendar.add(Calendar.DAY_OF_YEAR, -1);
115             counter.addRequestsAndErrors(getDayCounterAtDate(dayCalendar.getTime()));
116         }
117         counter.setStartDate(dayCalendar.getTime());
118     }
119
120     // compteur des 366 derniers jours
121     Counter getYearCounter() throws IOException {
122         final Counter yearCounter = createPeriodCounter("yyyy", currentDayCounter.getStartDate());
123         yearCounter.addRequestsAndErrors(currentDayCounter);
124         final Calendar dayCalendar = Calendar.getInstance();
125         final int currentMonth = dayCalendar.get(Calendar.MONTH);
126         dayCalendar.setTime(currentDayCounter.getStartDate());
127         dayCalendar.add(Calendar.DAY_OF_YEAR, -Period.ANNEE.getDurationDays() + 1);
128         yearCounter.setStartDate(dayCalendar.getTime());
129         for (int i = 1; i < Period.ANNEE.getDurationDays(); i++) {
130             if (dayCalendar.get(Calendar.DAY_OF_MONTH) == 1
131                     && dayCalendar.get(Calendar.MONTH) != currentMonth) {
132                 // optimisation : on récupère les statistiques précédemment calculées pour ce mois entier
133                 // au lieu de parcourir à chaque fois les statistiques de chaque jour du mois
134                 yearCounter.addRequestsAndErrors(getMonthCounterAtDate(dayCalendar.getTime()));
135                 final int nbDaysInMonth = dayCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
136                 // nbDaysInMonth - 1 puisque l'itération va ajouter 1 à i et à dayCalendar
137                 dayCalendar.add(Calendar.DAY_OF_YEAR, nbDaysInMonth - 1);
138                 i += nbDaysInMonth - 1;
139             } else {
140                 yearCounter.addRequestsAndErrors(getDayCounterAtDate(dayCalendar.getTime()));
141             }
142             dayCalendar.add(Calendar.DAY_OF_YEAR, 1);
143         }
144         return yearCounter;
145     }
146
147     private Counter getMonthCounterAtDate(Date day) throws IOException {
148         final Counter monthCounter = createMonthCounterAtDate(day);
149         try {
150             final Counter readCounter = new CounterStorage(monthCounter).readFromFile();
151             if (readCounter != null) {
152                 // monthCounter déjà calculé et enregistré
153                 return readCounter;
154             }
155         } catch (final IOException e) {
156             // lecture échouée, tant pis
157             // (on n'interrompt pas tout un rapport juste pour un des fichiers illisible)
158             LOG.info("read of a counter file failed: " + monthCounter.getName(), e);
159         }
160         // monthCounter n'est pas encore calculé (il est calculé à la fin de chaque mois,
161         // mais le serveur a pu aussi être arrêté ce jour là),
162         // alors on le calcule et on l'enregistre (optimisation pour getYearCounter)
163         final Calendar dayCalendar = Calendar.getInstance();
164         dayCalendar.setTime(day);
165         final int nbDaysInMonth = dayCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
166         for (int i = 1; i <= nbDaysInMonth; i++) {
167             dayCalendar.set(Calendar.DAY_OF_MONTH, i);
168             monthCounter.addRequestsAndErrors(getDayCounterAtDate(dayCalendar.getTime()));
169         }
170         monthCounter.writeToFile();
171         return monthCounter;
172     }
173
174     Counter createDayCounterAtDate(Date day) {
175         // le nom du compteur par jour est celui du compteur initial
176         // auquel on ajoute la date en suffixe pour que son enregistrement soit unique
177         return createPeriodCounter("yyyyMMdd", day);
178     }
179
180     private Counter createMonthCounterAtDate(Date day) {
181         // le nom du compteur par mois est celui du compteur initial
182         // auquel on ajoute le mois en suffixe pour que son enregistrement soit unique
183         return createPeriodCounter("yyyyMM", day);
184     }
185
186     private Counter createPeriodCounter(String dateFormatPattern, Date date) {
187         final String storageName = currentDayCounter.getName() + '_'
188                 + new SimpleDateFormat(dateFormatPattern, Locale.getDefault()).format(date);
189         // ceci crée une nouvelle instance sans requêtes avec startDate à la date courante
190         final Counter result = new Counter(currentDayCounter.getName(), storageName,
191                 currentDayCounter.getIconName(), currentDayCounter.getChildCounterName());
192         result.setApplication(currentDayCounter.getApplication());
193         result.setDisplayed(currentDayCounter.isDisplayed());
194         result.setRequestTransformPattern(currentDayCounter.getRequestTransformPattern());
195         result.setMaxRequestsCount(currentDayCounter.getMaxRequestsCount());
196         return result;
197     }
198 }
199