gt:         if (form.getQuery("initialYear") == null) {
context: /*
      This file is part of Cyclos (www.cyclos.org).
      A project of the Social Trade Organisation (www.socialtrade.org).
  
      Cyclos is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation; either version 2 of the License, or
      (at your option) any later version.
  
      Cyclos is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with Cyclos; if not, write to the Free Software
      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  
   */
  package nl.strohalm.cyclos.controls.reports.statistics;
  
  import java.lang.reflect.Constructor;
  import java.util.Calendar;
  import java.util.Collection;
  import java.util.GregorianCalendar;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import javax.servlet.http.HttpServletRequest;
  
  import nl.strohalm.cyclos.annotations.Inject;
  import nl.strohalm.cyclos.controls.ActionContext;
  import nl.strohalm.cyclos.controls.BaseQueryAction;
  import nl.strohalm.cyclos.controls.reports.statistics.graphs.StatisticalDataProducer;
  import nl.strohalm.cyclos.entities.accounts.AccountType;
  import nl.strohalm.cyclos.entities.accounts.SystemAccountType;
  import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilter;
  import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilterQuery;
  import nl.strohalm.cyclos.entities.accounts.transactions.TransferTypeQuery;
  import nl.strohalm.cyclos.entities.groups.Group;
  import nl.strohalm.cyclos.entities.groups.GroupFilter;
  import nl.strohalm.cyclos.entities.groups.GroupFilterQuery;
  import nl.strohalm.cyclos.entities.groups.GroupQuery;
  import nl.strohalm.cyclos.entities.reports.StatisticalQuery;
  import nl.strohalm.cyclos.entities.reports.StatisticsWhatToShow;
  import nl.strohalm.cyclos.entities.reports.ThroughTimeRange;
  import nl.strohalm.cyclos.entities.settings.LocalSettings;
  import nl.strohalm.cyclos.entities.settings.events.LocalSettingsChangeListener;
  import nl.strohalm.cyclos.entities.settings.events.LocalSettingsEvent;
  import nl.strohalm.cyclos.services.accounts.AccountTypeService;
  import nl.strohalm.cyclos.services.accounts.SystemAccountTypeQuery;
  import nl.strohalm.cyclos.services.groups.GroupFilterService;
  import nl.strohalm.cyclos.services.stats.StatisticalResultDTO;
  import nl.strohalm.cyclos.services.stats.StatisticalService;
  import nl.strohalm.cyclos.services.transfertypes.PaymentFilterService;
  import nl.strohalm.cyclos.utils.DateHelper;
  import nl.strohalm.cyclos.utils.Month;
  import nl.strohalm.cyclos.utils.NamedPeriod;
  import nl.strohalm.cyclos.utils.Period;
  import nl.strohalm.cyclos.utils.Quarter;
  import nl.strohalm.cyclos.utils.RequestHelper;
  import nl.strohalm.cyclos.utils.binding.BeanBinder;
  import nl.strohalm.cyclos.utils.binding.DataBinder;
  import nl.strohalm.cyclos.utils.binding.DataBinderHelper;
  import nl.strohalm.cyclos.utils.binding.PropertyBinder;
  import nl.strohalm.cyclos.utils.binding.SimpleCollectionBinder;
  import nl.strohalm.cyclos.utils.conversion.ReferenceConverter;
  import nl.strohalm.cyclos.utils.query.QueryParameters;
  
  import org.apache.commons.collections.CollectionUtils;
  
  /**
   * The common ancestor for all statistics actions. Defines some general methods for statistics.
   * 
   * @author Rinke
   * 
   */
  public abstract class StatisticsAction extends BaseQueryAction implements LocalSettingsChangeListener {
  
      /**
       * Enumeration to indicate what type of statistics will be shown
       */
      public enum StatisticsType {
          KEY_DEVELOPMENTS, MEMBER_ACTIVITIES, FINANCES, TAXES;
      }
  
      /**
       * binds the common fields to the form, such as periods and filters. Method to be called from the initDataBinder method
       * 
       * @param binder
       * @param settings
       */
      protected static void bindCommonFields(final BeanBinder<? extends StatisticalQuery> binder, final LocalSettings settings) {
          binder.registerBinder("periodMain", DataBinderHelper.namedPeriodBinder(settings, "periodMain"));
          binder.registerBinder("periodComparedTo", DataBinderHelper.namedPeriodBinder(settings, "periodComparedTo"));
          binder.registerBinder("throughTimeRange", PropertyBinder.instance(ThroughTimeRange.class, "throughTimeRange"));
          binder.registerBinder("initialMonth", PropertyBinder.instance(Month.class, "initialMonth"));
          binder.registerBinder("finalMonth", PropertyBinder.instance(Month.class, "finalMonth"));
          binder.registerBinder("initialQuarter", PropertyBinder.instance(Quarter.class, "initialQuarter"));
          binder.registerBinder("finalQuarter", PropertyBinder.instance(Quarter.class, "finalQuarter"));
          binder.registerBinder("initialYear", PropertyBinder.instance(Integer.class, "initialYear"));
          binder.registerBinder("initialMonthYear", PropertyBinder.instance(Integer.class, "initialMonthYear"));
          binder.registerBinder("initialQuarterYear", PropertyBinder.instance(Integer.class, "initialQuarterYear"));
          binder.registerBinder("finalYear", PropertyBinder.instance(Integer.class, "finalYear"));
          binder.registerBinder("finalMonthYear", PropertyBinder.instance(Integer.class, "finalMonthYear"));
          binder.registerBinder("finalQuarterYear", PropertyBinder.instance(Integer.class, "finalQuarterYear"));
          binder.registerBinder("paymentFilter", PropertyBinder.instance(PaymentFilter.class, "paymentFilter", ReferenceConverter.instance(PaymentFilter.class)));
          binder.registerBinder("paymentFilters", SimpleCollectionBinder.instance(PaymentFilter.class, "paymentFilters", ReferenceConverter.instance(PaymentFilter.class)));
          binder.registerBinder("groupFilters", SimpleCollectionBinder.instance(GroupFilter.class, "groupFilters"));
          binder.registerBinder("groups", SimpleCollectionBinder.instance(Group.class, "groups"));
          binder.registerBinder("systemAccountFilter", PropertyBinder.instance(SystemAccountType.class, "systemAccountFilter"));
          binder.registerBinder("whatToShow", PropertyBinder.instance(StatisticsWhatToShow.class, "whatToShow"));
      }
  
      /**
       * the Service. Child classes should assign this via
       * 
       * @inject setBlaService, calling StatisticsAction.setStatisticalService.
       */
      private StatisticalService                     statisticalService;
      private PaymentFilterService                   paymentFilterService;
      private AccountTypeService                     accountTypeService;
      private GroupFilterService                     groupFilterService;
  
      private DataBinder<? extends StatisticalQuery> dataBinder;
  
      public DataBinder<? extends StatisticalQuery> getDataBinder() {
          if (dataBinder == null) {
              final LocalSettings settings = settingsService.getLocalSettings();
              dataBinder = initDataBinder(settings);
          }
          return dataBinder;
      }
  
      /**
       * Returns the statistics type related to the action
       */
      public abstract StatisticsType getStatisticsType();
  
      /**
       * each subclass is forced to bind datafields via this method
       * 
       * @param settings
       */
      public abstract DataBinder<? extends StatisticalQuery> initDataBinder(final LocalSettings settings);
  
      /*
       * takes care that the transfer type filter box filled with appropriate transfer types. Method to be called from the prepareForm method. Currently
       * not used, but may be in future. If never used in future, then remove this and all associated.
       * 
       * @param context UNCOMMENT IF USED IN FUTURE
       */
      /*
       * protected void applyTransferTypeFilter(final ActionContext context) { final TransferTypeQuery ttQuery = resolveTransferTypeQuery(context); if
       * (ttQuery != null) { final List<TransferType> transferTypes = transferTypeService.search(ttQuery); final HttpServletRequest request =
       * context.getRequest(); request.setAttribute("transferTypeList", transferTypes); } }
       */
  
      @Override
      public void onLocalSettingsUpdate(final LocalSettingsEvent event) {
          super.onLocalSettingsUpdate(event);
          dataBinder = null;
      }
  
      @Inject
      public void setAccountTypeService(final AccountTypeService accountTypeService) {
          this.accountTypeService = accountTypeService;
      }
  
      @Inject
      public void setGroupFilterService(final GroupFilterService groupFilterService) {
          this.groupFilterService = groupFilterService;
      }
  
      @Inject
      public void setPaymentFilterService(final PaymentFilterService paymentFilterService) {
          this.paymentFilterService = paymentFilterService;
      }
  
      /**
       * takes care that a group filter box is filled with appropriate values. Method to be called from the prepareForm method.
       * 
       * @param request
       */
      protected void applyGroupFilter(final HttpServletRequest request) {
          final GroupQuery groupQuery = new GroupQuery();
          groupQuery.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
          request.setAttribute("groups", groupService.search(groupQuery));
          final GroupFilterQuery groupFilterQuery = new GroupFilterQuery();
          // no need to set anything on the GroupFilterQuery, as stats fall outside the scope of group managing permissions
          final Collection<GroupFilter> groupFilters = groupFilterService.search(groupFilterQuery);
          if (CollectionUtils.isNotEmpty(groupFilters)) {
              request.setAttribute("groupFilters", groupFilters);
          }
      }
  
      /**
       * takes care that the payment filter box filled with appropriate payment filters. Method to be called from the prepareForm method
       * 
       * @param request
       */
      protected void applyPaymentFilter(final HttpServletRequest request) {
          final PaymentFilterQuery pfQuery = new PaymentFilterQuery();
          pfQuery.setContext(PaymentFilterQuery.Context.REPORT);
          final List<PaymentFilter> paymentFilters = paymentFilterService.search(pfQuery);
          request.setAttribute("paymentFilterList", paymentFilters);
      }
  
      /**
       * takes care that the system account filter box filled with appropriate system accounts. Method to be called from the prepareForm method
       * 
       * @param request
       */
      protected void applySystemAccountFilter(final HttpServletRequest request) {
          final List<? extends AccountType> systemAccounts = accountTypeService.search(new SystemAccountTypeQuery());
          request.setAttribute("systemAccounts", systemAccounts);
      }
  
      /**
       * Takes care that both the named periods are filled with default values. To be called from the prepareForm method.
       * 
       * @param query
       * @param form
       */
      protected void bindPeriods(final StatisticalQuery query, final StatisticsForm form) {
          if (query.getPeriodMain().getEnd() == null) {
              final NamedPeriod periodMain = NamedPeriod.getLastQuarterPeriod();
              final NamedPeriod periodComparedTo = periodMain.getOneYearEarlier();
              bindPeriod("periodMain", form, periodMain);
              bindPeriod("periodComparedTo", form, periodComparedTo);
          }
      }
  
      // call this method via super() from the inhereting classes.
      @Override
      protected void executeQuery(final ActionContext context, final QueryParameters queryParameters) {
          final StatisticalQuery query = (StatisticalQuery) queryParameters;
          final HttpServletRequest request = context.getRequest();
          request.setAttribute("statisticsType", getStatisticsType());
          // If is through the time, calculate periods and set into the queryparams
          if (query.getWhatToShow() == StatisticsWhatToShow.THROUGH_TIME) {
              final ThroughTimeRange throughTimeRange = query.getThroughTimeRange();
              final Period period = getPeriodByTimeRange(query);
              final Period[] periods = DateHelper.getPeriodsThroughTheTime(period, throughTimeRange);
              query.setPeriods(periods);
          }
          // if groups are empty, but groupFilters are used, put all the groups from the groupFilter in the groups.
          final Collection<GroupFilter> groupFilters = query.getGroupFilters();
          final boolean hasGroupFilters = CollectionUtils.isNotEmpty(groupFilters);
          final boolean hasGroups = CollectionUtils.isNotEmpty(query.getGroups());
          if (hasGroupFilters && !hasGroups) {
              final Set<Group> groupsFromFilters = new HashSet<Group>();
              if (hasGroupFilters) {
                  // Get all groups from selected group filters
                  for (GroupFilter groupFilter : groupFilters) {
                      groupFilter = groupFilterService.load(groupFilter.getId(), GroupFilter.Relationships.GROUPS);
                      groupsFromFilters.addAll(groupFilter.getGroups());
                  }
                  query.setGroups(groupsFromFilters);
              }
          }
  
      }
  
      /**
       * gets the StatisticalService in its basic form as an instance of the StatisticalService class. Child classes should create a
       * getStatisticalService() method casting the StatisticalService instance to one of its subclasses
       * 
       * @return StatisticalService
       */
      protected StatisticalService getBaseStatisticalService() {
          return statisticalService;
      }
  
      /*
       * Create a period with its 'begin' and 'end' date corresponding to the first and second param on the period selection for months, quarters and
       * years.
       */
      protected Period getPeriodByTimeRange(final StatisticalQuery queryParameters) {
          final ThroughTimeRange throughTimeRange = queryParameters.getThroughTimeRange();
          Calendar calendarBegin = null;
          Calendar calendarEnd = null;
  
          if (throughTimeRange == ThroughTimeRange.MONTH) {
              calendarBegin = new GregorianCalendar(queryParameters.getInitialMonthYear(), queryParameters.getInitialMonth().getValue(), 1);
              final Calendar calendarEndAux = new GregorianCalendar(queryParameters.getFinalMonthYear(), queryParameters.getFinalMonth().getValue(), 1);
              calendarEnd = new GregorianCalendar(queryParameters.getFinalMonthYear(), queryParameters.getFinalMonth().getValue(), calendarEndAux.getActualMaximum(Calendar.DAY_OF_MONTH), 23, 59, 59);
  
          } else if (throughTimeRange == ThroughTimeRange.QUARTER) {
              final Quarter initialQuarter = queryParameters.getInitialQuarter();
              final Quarter finalQuarter = queryParameters.getFinalQuarter();
  
              calendarBegin = new GregorianCalendar(queryParameters.getInitialQuarterYear(), initialQuarter.getValue() * 3 - 3, 1);
              final Calendar calendarEndAux = new GregorianCalendar(queryParameters.getFinalQuarterYear(), finalQuarter.getValue() * 3 - 3, 1);
              calendarEnd = new GregorianCalendar(queryParameters.getFinalQuarterYear(), finalQuarter.getValue() * 3 - 3, calendarEndAux.getActualMaximum(Calendar.DAY_OF_MONTH), 23, 59, 59);
  
          } else if (throughTimeRange == ThroughTimeRange.YEAR) {
              calendarBegin = new GregorianCalendar(queryParameters.getInitialYear(), 0, 1);
              calendarEnd = new GregorianCalendar(queryParameters.getFinalYear(), 11, 31, 23, 59, 59);
          }
  
          final Period period = new Period(calendarBegin, calendarEnd);
          return period;
      }
  
      /**
       * This prepares the form. Some common fields are taken care of (Periods); all filters must be assigned by the descendant class prepareForm
       * method, which should call return super.prepareForm(context);
       */
      @Override
      protected QueryParameters prepareForm(final ActionContext context) {
          final StatisticsForm form = context.getForm();
          final StatisticalQuery query = getDataBinder().readFromString(form.getQuery());
          bindPeriods(query, form);
  
          // Send enums to JSP
          final HttpServletRequest request = context.getRequest();
          RequestHelper.storeEnum(request, StatisticsWhatToShow.class, "whatToShow");
          RequestHelper.storeEnum(request, ThroughTimeRange.class, "throughTimeRange");
          RequestHelper.storeEnum(request, Month.class, "months");
          RequestHelper.storeEnum(request, Quarter.class, "quarters");
  
          // Set default through time range
          if (form.getQuery("throughTimeRange") == null) {
              form.setQuery("throughTimeRange", ThroughTimeRange.MONTH);
          }
  
          // Set default initial and final months and years
          if (form.getQuery("initialMonth") == null) {
              final Map<String, Object> completedMonthAndYear = DateHelper.getLastCompletedMonthAndYear();
              final int lastCompletedMonth = (Integer) completedMonthAndYear.get("month");
              final int lastCompletedMonthYear = (Integer) completedMonthAndYear.get("year");
              form.setQuery("initialMonth", lastCompletedMonth);
              form.setQuery("initialMonthYear", lastCompletedMonthYear - 1);
              form.setQuery("finalMonth", lastCompletedMonth);
              form.setQuery("finalMonthYear", lastCompletedMonthYear);
          }
  
          // Set default initial and final quarters and years
          if (form.getQuery("initialQuarter") == null) {
              final Map<String, Object> completedQuarterAndYear = DateHelper.getLastCompletedQuarterAndYear();
              final Quarter lastCompletedQuarter = (Quarter) completedQuarterAndYear.get("quarter");
              final int lastCompletedQuarterYear = (Integer) completedQuarterAndYear.get("year");
              form.setQuery("initialQuarter", lastCompletedQuarter);
              form.setQuery("initialQuarterYear", lastCompletedQuarterYear - 1);
              form.setQuery("finalQuarter", lastCompletedQuarter);
              form.setQuery("finalQuarterYear", lastCompletedQuarterYear);
          }
  
          // Set default initial and final years
  