package org.gcube.data.access.accounting.summary.access.impl; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDateTime; import java.time.Period; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import org.gcube.data.access.accounting.summary.access.AccountingDao; import org.gcube.data.access.accounting.summary.access.ParameterException; import org.gcube.data.access.accounting.summary.access.impl.DBStructure.CONTEXTS; import org.gcube.data.access.accounting.summary.access.impl.DBStructure.DIMENSIONS; import org.gcube.data.access.accounting.summary.access.impl.DBStructure.Measure; import org.gcube.data.access.accounting.summary.access.model.MeasureResolution; import org.gcube.data.access.accounting.summary.access.model.Record; import org.gcube.data.access.accounting.summary.access.model.Report; import org.gcube.data.access.accounting.summary.access.model.ReportElement; import org.gcube.data.access.accounting.summary.access.model.ScopeDescriptor; import org.gcube.data.access.accounting.summary.access.model.Series; import org.gcube.data.access.accounting.summary.access.model.internal.Dimension; import org.gcube.data.access.accounting.summary.access.model.update.AccountingRecord; import org.gcube.data.access.accounting.summary.access.model.update.UpdateReport; import lombok.extern.slf4j.Slf4j; @Slf4j public class AccountingDaoImpl implements AccountingDao{ private ContextTreeProvider treeProvider=null; private ConnectionManager connectionManager=null; public AccountingDaoImpl() { connectionManager=new BasicConnectionManager(); treeProvider=new BasicContextTreeProvider(); } public void setTreeProvider(ContextTreeProvider treeProvider) { this.treeProvider = treeProvider; } public AccountingDaoImpl(ContextTreeProvider treeProvider, ConnectionManager connectionManager) { super(); this.treeProvider = treeProvider; this.connectionManager = connectionManager; } @Override public Report getReportByScope(ScopeDescriptor desc, Date from, Date to, MeasureResolution resolution) throws SQLException, ParameterException { DateTimeFormatter formatter=getFormatter(resolution); LocalDateTime fromDate=from.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime toDate=to.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); long startReportTime=System.currentTimeMillis(); log.info("Loading report {} for {} between {} and {} ",resolution,desc.getId(),formatter.format(fromDate),formatter.format(toDate)); Connection conn=connectionManager.getConnection(); Queries queries=new Queries(conn); int timeSlices=getRangeSize(from, to, resolution); //load available dimensions in time slice ResultSet dimensionRS=queries.getAvailableDimensions(from, to, desc, resolution); LinkedList foundDimensions=new LinkedList<>(); while(dimensionRS.next()){ String id=dimensionRS.getString(DIMENSIONS.ID); String label=dimensionRS.getString(DIMENSIONS.LABEL); String group=dimensionRS.getString(DIMENSIONS.GROUP); String aggregatedDim=dimensionRS.getString(DIMENSIONS.AGGREGATED_MEASURE); foundDimensions.add(new Dimension(id,label,aggregatedDim,group)); } log.debug("Found {} dimensions to load. ",foundDimensions.size()); // Prepare reports for each Dimension LinkedList reports=new LinkedList<>(); for(Dimension entry: foundDimensions){ String xLabel=entry.getLabel(); String yLabel="time"; String category=entry.getGroup(); // Report 1 series for selected Scope reports.add(new ReportElement(desc.getName()+" "+xLabel,category, xLabel,yLabel,new Series[]{getSeries(queries, from, to, entry, desc, resolution, timeSlices)})); // Report 2 series for each children if(desc.hasChildren()) { LinkedList childrenSeries=new LinkedList<>(); for(ScopeDescriptor child:desc.getChildren()){ childrenSeries.add(getSeries(queries, from, to, entry, child, resolution, timeSlices)); } reports.add(new ReportElement(desc.getName()+" children "+xLabel,category, xLabel,yLabel,childrenSeries.toArray(new Series[childrenSeries.size()]))); } // PreparedStatement psMeasure=queries.prepareMeasuresByDimension(desc, resolution); // // LocalDateTime toDate=to.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); // for(LocalDateTime toAsk=from.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); // toAsk.isAfter(toDate);toAsk=increment(toAsk,resolution)){ // // Scan for time slice // } } log.info("Loaded {} report elements in {} ms",reports.size(),(System.currentTimeMillis()-startReportTime)); return new Report(reports); } @Override public ScopeDescriptor getTree(Object request) throws Exception { return treeProvider.getTree(request); } @Override public Set getDimensions() throws SQLException { Connection conn=connectionManager.getConnection(); Queries q=new Queries(conn); ResultSet rs=q.listDimensions(); HashSet toReturn=new HashSet<>(); while(rs.next()) { String id=rs.getString(DIMENSIONS.ID); String label=rs.getString(DIMENSIONS.LABEL); String group=rs.getString(DIMENSIONS.GROUP); String aggregatedMeasure =rs.getString(DIMENSIONS.AGGREGATED_MEASURE); toReturn.add(new Dimension(id,label,aggregatedMeasure,group)); } return toReturn; } @Override public Set getContexts() throws SQLException { Connection conn=connectionManager.getConnection(); Queries q=new Queries(conn); ResultSet rs=q.listContexts(); HashSet toReturn=new HashSet<>(); while(rs.next()) { String id=rs.getString(CONTEXTS.ID); String label=rs.getString(CONTEXTS.LABEL); toReturn.add(new ScopeDescriptor(id,label)); } return toReturn; } @Override public UpdateReport insertRecords(AccountingRecord... toInsert) throws SQLException { log.trace("Preapring to insert {} records.",toInsert.length); Set existingDimensions=getDimensions(); Set existingContexts=getContexts(); Connection conn=connectionManager.getConnection(); log.debug("Loaded {} existing dimensions and {} contexts ",existingDimensions.size(),existingContexts.size()); Queries q=new Queries(conn); PreparedStatement psMeasure=q.getMeasureInsertionPreparedStatement(); PreparedStatement psContexts=q.getContextInsertionPreparedStatement(); PreparedStatement psDimensions=q.getDimensionInsertionPreparedStatement(); long insertMeasureCounter=0l; Set insertedDimensions=new HashSet<>(); Set insertedContexts=new HashSet<>(); log.debug("Actually registering records.."); for(AccountingRecord record:toInsert) { Dimension dim=record.getDimension(); ScopeDescriptor context=record.getContext(); if(!existingDimensions.contains(dim)) { log.debug("Registering {} ",dim); //ID,Label,Group,AGG psDimensions.setString(1, dim.getId()); psDimensions.setString(2, dim.getLabel()); psDimensions.setString(3, dim.getGroup()); psDimensions.setString(4, dim.getAggregatedMeasure()); if(psDimensions.executeUpdate()==0)throw new SQLException("Error registering Dimension : No inserted rows"); insertedDimensions.add(dim); } if(!existingContexts.contains(context)) { log.debug("Registering {} ",context); //ID,Label psContexts.setString(1, context.getId()); psContexts.setString(2, context.getName()); if(psContexts.executeUpdate()==0)throw new SQLException("Error registering Context : No inserted rows"); insertedContexts.add(context); } //Context, Dim,time,measure psMeasure.setString(1, context.getId()); psMeasure.setString(2, dim.getId()); psMeasure.setLong(3, record.getTime().getTime()); psMeasure.setLong(4, record.getMeasure()); psMeasure.setLong(5, record.getMeasure()); if(psMeasure.executeUpdate()==0) throw new SQLException("Error registering Measure : No inserted rows"); insertMeasureCounter++; } conn.commit(); log.trace("Done inserting {} rows. Registered {} dimensions and {} contexts.",insertMeasureCounter,insertedDimensions.size(),insertedContexts.size()); return new UpdateReport(insertMeasureCounter,insertedContexts,insertedDimensions); } private static final int getRangeSize(Date from, Date to, MeasureResolution resolution) throws ParameterException { log.debug("Evaluating time range between {} , {} [{}]",from,to,resolution); Period p=Period.between(from.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), to.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); switch(resolution) { case MONTHLY : return p.getMonths()+(p.getYears()*12); default : throw new ParameterException("Invalid resolution "+resolution); } } private static LocalDateTime increment(LocalDateTime toIncrement,MeasureResolution res,int offset){ switch(res){ case MONTHLY : return toIncrement.plusMonths(1); default : throw new RuntimeException("Unexpected Resolution "+res); } } private static final DateTimeFormatter monthFormatter=DateTimeFormatter.ofPattern("yyyy-MM"); private static DateTimeFormatter getFormatter(MeasureResolution res){ switch(res){ case MONTHLY : return monthFormatter; default : throw new RuntimeException("Unexpected Resolution "+res); } } private Series getSeries(Queries queries, Date from, Date to, Dimension dim, ScopeDescriptor scope, MeasureResolution res, int timeSlices) throws SQLException{ Record[] records=new Record[timeSlices]; PreparedStatement ps=queries.prepareMeasuresByDimension(scope, res); DateTimeFormatter formatter=getFormatter(res); LocalDateTime toSetStartDate=from.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); for(int i=0;i