/**
* Copyright (c) 2012, The National Archives <pronom@nationalarchives.gsi.gov.uk>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following
* conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the The National Archives nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package uk.gov.nationalarchives.droid.report;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import uk.gov.nationalarchives.droid.core.interfaces.config.DroidGlobalConfig;
import uk.gov.nationalarchives.droid.core.interfaces.filter.expressions.Criterion;
import uk.gov.nationalarchives.droid.core.interfaces.filter.Filter;
import uk.gov.nationalarchives.droid.planet.xml.dao.PlanetsXMLData;
import uk.gov.nationalarchives.droid.profile.ProfileContextLocator;
import uk.gov.nationalarchives.droid.profile.ProfileInstance;
import uk.gov.nationalarchives.droid.profile.ProfileInstanceManager;
import uk.gov.nationalarchives.droid.report.dao.ReportLineItem;
import uk.gov.nationalarchives.droid.report.interfaces.CancellableProgressObserver;
import uk.gov.nationalarchives.droid.report.interfaces.GroupedFieldItem;
import uk.gov.nationalarchives.droid.report.interfaces.Report;
import uk.gov.nationalarchives.droid.report.interfaces.ReportCancelledException;
import uk.gov.nationalarchives.droid.report.interfaces.ReportItem;
import uk.gov.nationalarchives.droid.report.interfaces.ReportManager;
import uk.gov.nationalarchives.droid.report.interfaces.ReportRequest;
import uk.gov.nationalarchives.droid.report.interfaces.ReportSpec;
import uk.gov.nationalarchives.droid.report.interfaces.ReportSpecDao;
import uk.gov.nationalarchives.droid.report.interfaces.ReportSpecItem;
import uk.gov.nationalarchives.droid.report.planets.xml.PlanetsXMLGenerator;
import uk.gov.nationalarchives.droid.results.handlers.ProgressObserver;
/**
* @author Alok Kumar Dash
*/
//CHECKSTYLE:OFF - this class has a fan out complexity one more than allowed
// This is the number of classes this class depends on.
// Quick solution is to strip out the planets xml generator as a special case,
// once we're happy the improved reporting engine can handle them too.
// But it's still probably too complex in some ways.
public class ReportManagerImpl implements ReportManager {
//CHECKSTYLE:ON
private final Log log = LogFactory.getLog(getClass());
private ProfileContextLocator profileContextLocator;
private ReportSpecDao reportSpecDao;
private ProgressObserver observer;
private DroidGlobalConfig config;
/**
* {@inheritDoc}
* @deprecated PLANETS XML is now built using XSLT to transform normal report XML.
*/
@Override
@Deprecated
public void generatePlanetsXML(String profileId, String nameAndPathOfTheFile) {
long startTime = System.currentTimeMillis();
if (!profileContextLocator.hasProfileContext(profileId)) {
throw new RuntimeException("Profile not available");
}
ProfileInstance profile = profileContextLocator.getProfileInstance(profileId);
ProfileInstanceManager profileInstancemanager = profileContextLocator.openProfileInstanceManager(profile);
PlanetsXMLData planetsData = profileInstancemanager.getPlanetsData();
planetsData.getProfileStat().setProfileStartDate(profile.getProfileStartDate());
planetsData.getProfileStat().setProfileEndDate(profile.getProfileEndDate());
planetsData.getProfileStat().setProfileSaveDate(profile.getDateCreated());
planetsData.setTopLevelItems(ReportUtils.toResourcePaths(profile.getProfileSpec().getResources()));
PlanetsXMLGenerator planetXMLGenerator = new PlanetsXMLGenerator(observer, nameAndPathOfTheFile, planetsData);
planetXMLGenerator.generate();
long stopTime = System.currentTimeMillis();
log.info(String.format("Time for profile [%s]: %s ms", profileId, stopTime - startTime));
}
/**
* {@inheritDoc}
*/
@Override
public Report generateReport(ReportRequest request, Filter optionalFilter,
CancellableProgressObserver progressObserver)
throws ReportCancelledException {
log.info(String.format("Generating report: %s", request.getReportSpec().getName()));
final int totalSteps = request.getProfileIds().size() * request.getReportSpec().getItems().size();
int stepCount = 0;
Report report = new Report();
report.setTitle(request.getReportSpec().getName());
for (ReportSpecItem specItem : request.getReportSpec().getItems()) {
ReportItem item = new ReportItem();
item.setReportSpecItem(specItem);
report.addItem(item);
Map<String, GroupedFieldItem> groups = new LinkedHashMap<String, GroupedFieldItem>();
for (String profileId : request.getProfileIds()) {
ProfileInstance profile = profileContextLocator.getProfileInstance(profileId);
report.addProfile(profile);
ProfileInstanceManager profileInstanceManager =
profileContextLocator.openProfileInstanceManager(profile);
Filter filterToUse = optionalFilter == null ? profile.getFilter() : optionalFilter;
Criterion filter = ReportUtils.buildFilter(filterToUse, specItem.getFilter());
List<ReportLineItem> reportData = profileInstanceManager.getReportData(
filter, specItem.getField(), specItem.getGroupByFields());
for (ReportLineItem reportLineItem : reportData) {
final List<String> groupByValues = reportLineItem.getGroupByValues();
final String groupKey = getStringListKey(groupByValues);
if (!groups.containsKey(groupKey)) {
GroupedFieldItem newGroup = new GroupedFieldItem();
newGroup.setValues(groupByValues);
item.addGroupedFieldItem(newGroup);
groups.put(groupKey, newGroup);
}
GroupedFieldItem groupedFieldItem = groups.get(groupKey);
groupedFieldItem.addProfileData(
ReportUtils.buildProfileReportData(profile, reportLineItem));
}
if (progressObserver != null) {
progressObserver.onProgress((ProgressObserver.UNITY_PERCENT * stepCount++) / totalSteps);
if (progressObserver.isCancelled()) {
throw new ReportCancelledException();
}
}
}
}
return report;
}
private String getStringListKey(List<String> values) {
String result = "";
for (String value : values) {
result += value + "@";
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public List<ReportSpec> listReportSpecs() {
File reportDefDir = config.getReportDefinitionDir();
FileFilter xslFileFilter = new FileFilter() {
@Override
public boolean accept(File f) {
// Only accept files with names conforming to the pattern:
// {DESCRIPTION}.{EXTENSION}.XSL
boolean result = false;
final String name = f.getName();
if (FilenameUtils.isExtension(name, "xsl")) {
final String baseName = FilenameUtils.getBaseName(name);
result = baseName.indexOf('.') > -1;
}
return result;
}
};
List<File> globalTransforms = new ArrayList<File>();
for (File transform : reportDefDir.listFiles(xslFileFilter)) {
globalTransforms.add(transform);
}
List<ReportSpec> reportSpecs = new ArrayList<ReportSpec>();
FileFilter xmlFileFilter = new FileFilter() {
@Override
public boolean accept(File f) {
return FilenameUtils.isExtension(f.getName(), "xml");
}
};
for (File reportDir : reportDefDir.listFiles()) {
if (reportDir.isDirectory()) {
// Get any local transforms in the directory:
List<File> reportTransforms = new ArrayList<File>();
for (File transform : reportDir.listFiles(xslFileFilter)) {
reportTransforms.add(transform);
}
reportTransforms.addAll(globalTransforms);
// Get any reports in the directory:
for (File reportDef : reportDir.listFiles(xmlFileFilter)) {
if (reportDef.isFile()) {
ReportSpec reportSpec;
reportSpec = reportSpecDao.readReportSpec(reportDef.getPath());
reportSpec.setXslTransforms(reportTransforms);
reportSpecs.add(reportSpec);
}
}
}
}
return reportSpecs;
}
/**
* @param profileContextLocator
* the profileContextLocator to set
*/
public void setProfileContextLocator(ProfileContextLocator profileContextLocator) {
this.profileContextLocator = profileContextLocator;
}
/**
* @param observer
* the observer to set
*/
public void setObserver(ProgressObserver observer) {
this.observer = observer;
}
/**
* @param reportSpecDao the reportSpecDao to set
*/
public void setReportSpecDao(ReportSpecDao reportSpecDao) {
this.reportSpecDao = reportSpecDao;
}
/**
* Initialises the bean's working directories, if they don't already exist.
* @throws IOException if a report definition resource could not be copied to the report def directory.
*/
public void init() throws IOException {
ReportUtils.populateReportDefinitionsDirectory(config.getReportDefinitionDir(), getClass().getClassLoader());
}
/**
* @param config the config to set
*/
public void setConfig(DroidGlobalConfig config) {
this.config = config;
}
}