/*
* Copyright 2008-2017 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.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* Factory pour les compteurs par jour, par semaine, par mois et par année.
* @author Emeric Vernat
*/
class PeriodCounterFactory {
// Note d'implémentation : Calendar.getInstance() crée à chaque appel une nouvelle instance
// de Calendar à la date et à l'heure courante (cette date-heure peut être modifiée)
private final Counter currentDayCounter;
PeriodCounterFactory(Counter currentDayCounter) {
super();
assert currentDayCounter != null;
this.currentDayCounter = currentDayCounter;
}
Counter buildNewDayCounter() throws IOException {
final Calendar start = Calendar.getInstance();
start.setTime(currentDayCounter.getStartDate());
if (start.get(Calendar.MONTH) != Calendar.getInstance().get(Calendar.MONTH)) {
// le mois a changé, on crée un compteur vide qui sera enregistré dans un nouveau fichier;
// ce compteur agrégé pour le mois est utilisé pour de meilleurs performances sur le compteur de l'année
// on calcule le monthCounter et on l'enregistre (optimisation pour getYearCounter)
getMonthCounterAtDate(currentDayCounter.getStartDate());
}
return createDayCounterAtDate(new Date());
}
// compteur d'un jour donné
private Counter getDayCounterAtDate(Date day) {
final Counter dayCounter = createDayCounterAtDate(day);
try {
dayCounter.readFromFile();
} catch (final IOException e) {
// lecture échouée, tant pis
// (on n'interrompt pas tout un rapport juste pour un des fichiers illisible)
LOG.info("read of a counter file failed: " + dayCounter.getName(), e);
}
return dayCounter;
}
// compteur custom
Counter getCustomCounter(Range range) {
assert range.getPeriod() == null;
final Counter customCounter = createPeriodCounter("yyyy-MM-dd", range.getStartDate());
addRequestsAndErrorsForRange(customCounter, range);
return customCounter;
}
// compteur du jour courant
Counter getDayCounter() {
return currentDayCounter;
}
// compteur des 7 derniers jours
Counter getWeekCounter() {
final Counter weekCounter = createPeriodCounter("yyyyWW", currentDayCounter.getStartDate());
addRequestsAndErrorsForRange(weekCounter, Period.SEMAINE.getRange());
return weekCounter;
}
// compteur des 31 derniers jours,
// ici c'est un mois flottant (ie une durée), et pas un mois entier
Counter getMonthCounter() {
final Counter monthCounter = createMonthCounterAtDate(currentDayCounter.getStartDate());
addRequestsAndErrorsForRange(monthCounter, Period.MOIS.getRange());
return monthCounter;
}
private void addRequestsAndErrorsForRange(Counter counter, Range range) {
final Calendar dayCalendar = Calendar.getInstance();
if (range.getPeriod() == null) {
counter.addRequestsAndErrors(getDayCounterAtDate(range.getEndDate()));
dayCalendar.setTime(range.getEndDate());
// issue 122: attention endDate contient un jour jusqu'à 23h59m59s selon Range.parse
dayCalendar.set(Calendar.HOUR_OF_DAY, 0);
dayCalendar.set(Calendar.MINUTE, 0);
dayCalendar.set(Calendar.SECOND, 0);
} else {
counter.addRequestsAndErrors(currentDayCounter);
dayCalendar.setTime(currentDayCounter.getStartDate());
}
final int durationDays = range.getDurationDays();
for (int i = 1; i < durationDays; i++) {
// TODO optimisation avec getMonthCounterAtDate comme getYearCounter() ?
dayCalendar.add(Calendar.DAY_OF_YEAR, -1);
counter.addRequestsAndErrors(getDayCounterAtDate(dayCalendar.getTime()));
}
counter.setStartDate(dayCalendar.getTime());
}
// compteur des 366 derniers jours
Counter getYearCounter() throws IOException {
final Counter yearCounter = createPeriodCounter("yyyy", currentDayCounter.getStartDate());
yearCounter.addRequestsAndErrors(currentDayCounter);
final Calendar dayCalendar = Calendar.getInstance();
final int currentMonth = dayCalendar.get(Calendar.MONTH);
dayCalendar.setTime(currentDayCounter.getStartDate());
dayCalendar.add(Calendar.DAY_OF_YEAR, -Period.ANNEE.getDurationDays() + 1);
yearCounter.setStartDate(dayCalendar.getTime());
for (int i = 1; i < Period.ANNEE.getDurationDays(); i++) {
if (dayCalendar.get(Calendar.DAY_OF_MONTH) == 1
&& dayCalendar.get(Calendar.MONTH) != currentMonth) {
// optimisation : on récupère les statistiques précédemment calculées pour ce mois entier
// au lieu de parcourir à chaque fois les statistiques de chaque jour du mois
yearCounter.addRequestsAndErrors(getMonthCounterAtDate(dayCalendar.getTime()));
final int nbDaysInMonth = dayCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
// nbDaysInMonth - 1 puisque l'itération va ajouter 1 à i et à dayCalendar
dayCalendar.add(Calendar.DAY_OF_YEAR, nbDaysInMonth - 1);
i += nbDaysInMonth - 1;
} else {
yearCounter.addRequestsAndErrors(getDayCounterAtDate(dayCalendar.getTime()));
}
dayCalendar.add(Calendar.DAY_OF_YEAR, 1);
}
return yearCounter;
}
private Counter getMonthCounterAtDate(Date day) throws IOException {
final Counter monthCounter = createMonthCounterAtDate(day);
try {
final Counter readCounter = new CounterStorage(monthCounter).readFromFile();
if (readCounter != null) {
// monthCounter déjà calculé et enregistré
return readCounter;
}
} catch (final IOException e) {
// lecture échouée, tant pis
// (on n'interrompt pas tout un rapport juste pour un des fichiers illisible)
LOG.info("read of a counter file failed: " + monthCounter.getName(), e);
}
// monthCounter n'est pas encore calculé (il est calculé à la fin de chaque mois,
// mais le serveur a pu aussi être arrêté ce jour là),
// alors on le calcule et on l'enregistre (optimisation pour getYearCounter)
final Calendar dayCalendar = Calendar.getInstance();
dayCalendar.setTime(day);
final int nbDaysInMonth = dayCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
for (int i = 1; i <= nbDaysInMonth; i++) {
dayCalendar.set(Calendar.DAY_OF_MONTH, i);
monthCounter.addRequestsAndErrors(getDayCounterAtDate(dayCalendar.getTime()));
}
monthCounter.writeToFile();
return monthCounter;
}
Counter createDayCounterAtDate(Date day) {
// le nom du compteur par jour est celui du compteur initial
// auquel on ajoute la date en suffixe pour que son enregistrement soit unique
return createPeriodCounter("yyyyMMdd", day);
}
private Counter createMonthCounterAtDate(Date day) {
// le nom du compteur par mois est celui du compteur initial
// auquel on ajoute le mois en suffixe pour que son enregistrement soit unique
return createPeriodCounter("yyyyMM", day);
}
private Counter createPeriodCounter(String dateFormatPattern, Date date) {
final String storageName = currentDayCounter.getName() + '_'
+ new SimpleDateFormat(dateFormatPattern, Locale.getDefault()).format(date);
// ceci crée une nouvelle instance sans requêtes avec startDate à la date courante
final Counter result = new Counter(currentDayCounter.getName(), storageName,
currentDayCounter.getIconName(), currentDayCounter.getChildCounterName());
result.setApplication(currentDayCounter.getApplication());
result.setDisplayed(currentDayCounter.isDisplayed());
result.setRequestTransformPattern(currentDayCounter.getRequestTransformPattern());
result.setMaxRequestsCount(currentDayCounter.getMaxRequestsCount());
return result;
}
}