/* =================================================================== * DaoQueryBiz.java * * Created Aug 5, 2009 12:31:45 PM * * Copyright (c) 2009 Solarnetwork.net Dev Team. * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * =================================================================== */ package net.solarnetwork.central.query.biz.dao; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.ReadableInstant; import org.joda.time.ReadableInterval; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import net.solarnetwork.central.dao.FilterableDao; import net.solarnetwork.central.dao.PriceLocationDao; import net.solarnetwork.central.dao.SolarLocationDao; import net.solarnetwork.central.dao.WeatherLocationDao; import net.solarnetwork.central.datum.dao.GeneralLocationDatumDao; import net.solarnetwork.central.datum.dao.GeneralNodeDatumDao; import net.solarnetwork.central.datum.domain.AggregateGeneralLocationDatumFilter; import net.solarnetwork.central.datum.domain.AggregateGeneralNodeDatumFilter; import net.solarnetwork.central.datum.domain.DatumFilterCommand; import net.solarnetwork.central.datum.domain.GeneralLocationDatumFilter; import net.solarnetwork.central.datum.domain.GeneralLocationDatumFilterMatch; import net.solarnetwork.central.datum.domain.GeneralNodeDatumFilter; import net.solarnetwork.central.datum.domain.GeneralNodeDatumFilterMatch; import net.solarnetwork.central.datum.domain.ReportingGeneralLocationDatumMatch; import net.solarnetwork.central.datum.domain.ReportingGeneralNodeDatumMatch; import net.solarnetwork.central.domain.Aggregation; import net.solarnetwork.central.domain.Entity; import net.solarnetwork.central.domain.Filter; import net.solarnetwork.central.domain.FilterResults; import net.solarnetwork.central.domain.Location; import net.solarnetwork.central.domain.LocationMatch; import net.solarnetwork.central.domain.PriceLocation; import net.solarnetwork.central.domain.SortDescriptor; import net.solarnetwork.central.domain.SourceLocation; import net.solarnetwork.central.domain.SourceLocationMatch; import net.solarnetwork.central.domain.WeatherLocation; import net.solarnetwork.central.query.biz.QueryBiz; import net.solarnetwork.central.query.domain.ReportableInterval; /** * Implementation of {@link QueryBiz}. * * @author matt * @version 2.1 */ public class DaoQueryBiz implements QueryBiz { private GeneralNodeDatumDao generalNodeDatumDao; private GeneralLocationDatumDao generalLocationDatumDao; private SolarLocationDao solarLocationDao; private int filteredResultsLimit = 1000; private long maxDaysForMinuteAggregation = 7; private long maxDaysForHourAggregation = 31; private long maxDaysForDayAggregation = 730; private long maxDaysForDayOfWeekAggregation = 3650; private long maxDaysForHourOfDayAggregation = 3650; private final Logger log = LoggerFactory.getLogger(getClass()); private final Map<Class<? extends Entity<?>>, FilterableDao<SourceLocationMatch, Long, SourceLocation>> filterLocationDaoMapping; /** * Default constructor. */ public DaoQueryBiz() { super(); filterLocationDaoMapping = new HashMap<Class<? extends Entity<?>>, FilterableDao<SourceLocationMatch, Long, SourceLocation>>( 4); } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public ReportableInterval getReportableInterval(Long nodeId, String sourceId) { ReadableInterval interval = generalNodeDatumDao.getReportableInterval(nodeId, sourceId); if ( interval == null ) { return null; } DateTimeZone tz = null; if ( interval.getChronology() != null ) { tz = interval.getChronology().getZone(); } return new ReportableInterval(interval, (tz == null ? null : tz.toTimeZone())); } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public Set<String> getAvailableSources(Long nodeId, DateTime start, DateTime end) { return generalNodeDatumDao.getAvailableSources(nodeId, start, end); } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public FilterResults<GeneralNodeDatumFilterMatch> findFilteredGeneralNodeDatum( GeneralNodeDatumFilter filter, List<SortDescriptor> sortDescriptors, Integer offset, Integer max) { return generalNodeDatumDao.findFiltered(filter, sortDescriptors, limitFilterOffset(offset), limitFilterMaximum(max)); } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public FilterResults<ReportingGeneralNodeDatumMatch> findFilteredAggregateGeneralNodeDatum( AggregateGeneralNodeDatumFilter filter, List<SortDescriptor> sortDescriptors, Integer offset, Integer max) { return generalNodeDatumDao.findAggregationFiltered(enforceGeneralAggregateLevel(filter), sortDescriptors, limitFilterOffset(offset), limitFilterMaximum(max)); } private Aggregation enforceAggregation(final Aggregation agg, ReadableInstant s, ReadableInstant e, Filter filter) { Aggregation forced = null; if ( agg == Aggregation.RunningTotal ) { // running total return null; } if ( s == null && e != null ) { // treat start date as SolarNetwork epoch (may want to make epoch configurable) s = new DateTime(2008, 1, 1, 0, 0, 0, DateTimeZone.UTC); } else if ( s != null && e == null ) { // treat end date as now for purposes of this calculating query range e = new DateTime(); } long diffDays = (s != null && e != null ? (e.getMillis() - s.getMillis()) / (1000L * 60L * 60L * 24L) : 0); if ( s == null && e == null && (agg == null || agg.compareTo(Aggregation.Day) < 0) && agg != Aggregation.HourOfDay && agg != Aggregation.SeasonalHourOfDay && agg != Aggregation.DayOfWeek && agg != Aggregation.SeasonalDayOfWeek ) { log.info("Restricting aggregate to Day level for filter with missing start or end date: {}", filter); forced = Aggregation.Day; } else if ( agg == Aggregation.HourOfDay || agg == Aggregation.SeasonalHourOfDay ) { if ( diffDays > maxDaysForHourOfDayAggregation ) { log.info("Restricting aggregate to Month level for filter duration {} days (> {}): {}", diffDays, maxDaysForHourOfDayAggregation, filter); forced = Aggregation.Month; } } else if ( agg == Aggregation.DayOfWeek || agg == Aggregation.SeasonalDayOfWeek ) { if ( diffDays > maxDaysForDayOfWeekAggregation ) { log.info("Restricting aggregate to Month level for filter duration {} days (> {}): {}", diffDays, maxDaysForDayOfWeekAggregation, filter); forced = Aggregation.Month; } } else if ( diffDays > maxDaysForDayAggregation && (agg == null || agg.compareLevel(Aggregation.Month) < 0) ) { log.info("Restricting aggregate to Month level for filter duration {} days (> {}): {}", diffDays, maxDaysForDayAggregation, filter); forced = Aggregation.Month; } else if ( diffDays > maxDaysForHourAggregation && (agg == null || agg.compareLevel(Aggregation.Day) < 0) ) { log.info("Restricting aggregate to Day level for filter duration {} days (> {}): {}", diffDays, maxDaysForHourAggregation, filter); forced = Aggregation.Day; } else if ( diffDays > maxDaysForMinuteAggregation && (agg == null || agg.compareTo(Aggregation.Hour) < 0) ) { log.info("Restricting aggregate to Hour level for filter duration {} days (> {}): {}", diffDays, maxDaysForMinuteAggregation, filter); forced = Aggregation.Hour; } return (forced != null ? forced : agg); } private AggregateGeneralNodeDatumFilter enforceGeneralAggregateLevel( AggregateGeneralNodeDatumFilter filter) { if ( filter.isMostRecent() ) { return filter; } Aggregation forced = enforceAggregation(filter.getAggregation(), filter.getStartDate(), filter.getEndDate(), filter); if ( forced != null ) { DatumFilterCommand cmd = new DatumFilterCommand(); cmd.setAggregate(forced); cmd.setEndDate(filter.getEndDate()); cmd.setNodeIds(filter.getNodeIds()); cmd.setSourceIds(filter.getSourceIds()); cmd.setStartDate(filter.getStartDate()); cmd.setDataPath(filter.getDataPath()); return cmd; } return filter; } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public FilterResults<LocationMatch> findFilteredLocations(Location filter, List<SortDescriptor> sortDescriptors, Integer offset, Integer max) { if ( filter == null || filter.getFilter() == null || filter.getFilter().isEmpty() ) { throw new IllegalArgumentException("Filter is required."); } return solarLocationDao.findFiltered(filter, sortDescriptors, limitFilterOffset(offset), limitFilterMaximum(max)); } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public FilterResults<GeneralLocationDatumFilterMatch> findGeneralLocationDatum( GeneralLocationDatumFilter filter, List<SortDescriptor> sortDescriptors, Integer offset, Integer max) { return generalLocationDatumDao.findFiltered(filter, sortDescriptors, limitFilterOffset(offset), limitFilterMaximum(max)); } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public FilterResults<ReportingGeneralLocationDatumMatch> findAggregateGeneralLocationDatum( AggregateGeneralLocationDatumFilter filter, List<SortDescriptor> sortDescriptors, Integer offset, Integer max) { return generalLocationDatumDao.findAggregationFiltered(enforceGeneralAggregateLevel(filter), sortDescriptors, limitFilterOffset(offset), limitFilterMaximum(max)); } private AggregateGeneralLocationDatumFilter enforceGeneralAggregateLevel( AggregateGeneralLocationDatumFilter filter) { Aggregation forced = enforceAggregation(filter.getAggregation(), filter.getStartDate(), filter.getEndDate(), filter); if ( forced != null ) { DatumFilterCommand cmd = new DatumFilterCommand(); cmd.setAggregate(forced); cmd.setEndDate(filter.getEndDate()); cmd.setLocationIds(filter.getLocationIds()); cmd.setSourceIds(filter.getSourceIds()); cmd.setStartDate(filter.getStartDate()); cmd.setDataPath(filter.getDataPath()); return cmd; } return filter; } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public Set<String> getLocationAvailableSources(Long locationId, DateTime start, DateTime end) { return generalLocationDatumDao.getAvailableSources(locationId, start, end); } @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public ReportableInterval getLocationReportableInterval(Long locationId, String sourceId) { ReadableInterval interval = generalLocationDatumDao.getReportableInterval(locationId, sourceId); if ( interval == null ) { return null; } DateTimeZone tz = null; if ( interval.getChronology() != null ) { tz = interval.getChronology().getZone(); } return new ReportableInterval(interval, (tz == null ? null : tz.toTimeZone())); } private Integer limitFilterMaximum(Integer requestedMaximum) { if ( requestedMaximum == null || requestedMaximum.intValue() > filteredResultsLimit || requestedMaximum.intValue() < 1 ) { return filteredResultsLimit; } return requestedMaximum; } private Integer limitFilterOffset(Integer requestedOffset) { if ( requestedOffset == null || requestedOffset.intValue() < 0 ) { return 0; } return requestedOffset; } public int getFilteredResultsLimit() { return filteredResultsLimit; } public void setFilteredResultsLimit(int filteredResultsLimit) { this.filteredResultsLimit = filteredResultsLimit; } @Autowired public void setPriceLocationDao(PriceLocationDao priceLocationDao) { filterLocationDaoMapping.put(PriceLocation.class, priceLocationDao); } @Autowired public void setWeatherLocationDao(WeatherLocationDao weatherLocationDao) { filterLocationDaoMapping.put(WeatherLocation.class, weatherLocationDao); } public GeneralNodeDatumDao getGeneralNodeDatumDao() { return generalNodeDatumDao; } @Autowired public void setGeneralNodeDatumDao(GeneralNodeDatumDao generalNodeDatumDao) { this.generalNodeDatumDao = generalNodeDatumDao; } public void setMaxDaysForMinuteAggregation(long maxDaysForMinuteAggregation) { this.maxDaysForMinuteAggregation = maxDaysForMinuteAggregation; } public void setMaxDaysForHourAggregation(long maxDaysForHourAggregation) { this.maxDaysForHourAggregation = maxDaysForHourAggregation; } public void setMaxDaysForDayAggregation(long maxDaysForDayAggregation) { this.maxDaysForDayAggregation = maxDaysForDayAggregation; } public void setMaxDaysForDayOfWeekAggregation(long maxDaysForDayOfWeekAggregation) { this.maxDaysForDayOfWeekAggregation = maxDaysForDayOfWeekAggregation; } public void setMaxDaysForHourOfDayAggregation(long maxDaysForHourOfDayAggregation) { this.maxDaysForHourOfDayAggregation = maxDaysForHourOfDayAggregation; } public SolarLocationDao getSolarLocationDao() { return solarLocationDao; } @Autowired public void setSolarLocationDao(SolarLocationDao solarLocationDao) { this.solarLocationDao = solarLocationDao; } public GeneralLocationDatumDao getGeneralLocationDatumDao() { return generalLocationDatumDao; } @Autowired public void setGeneralLocationDatumDao(GeneralLocationDatumDao generalLocationDatumDao) { this.generalLocationDatumDao = generalLocationDatumDao; } }