/*==========================================================================*\
| $Id: ReportTemplate.java,v 1.3 2011/05/27 15:36:46 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2011 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT 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 Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.reporter;
import java.io.File;
import java.io.FileOutputStream;
import java.text.DateFormat;
import org.webcat.oda.commons.DataSetDescription;
import org.webcat.oda.commons.DataSetMetadata;
import org.webcat.oda.commons.ReportMetadata;
import org.apache.log4j.Logger;
import org.eclipse.birt.report.model.api.DataSetHandle;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.birt.report.model.api.SessionHandle;
import org.eclipse.birt.report.model.api.activity.SemanticException;
import org.webcat.core.MutableArray;
import org.webcat.core.User;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation.NSSet;
import com.webobjects.foundation.NSTimestamp;
import er.extensions.foundation.ERXArrayUtilities;
// -------------------------------------------------------------------------
/**
* Represents a BIRT report template and its associated metadata.
*
* @author Tony Allevato
* @author Last changed by $Author: stedwar2 $
* @version $Revision: 1.3 $, $Date: 2011/05/27 15:36:46 $
*/
public class ReportTemplate extends _ReportTemplate
{
//~ Constructor ...........................................................
// ----------------------------------------------------------
/**
* Creates a new ReportTemplate object.
*/
public ReportTemplate()
{
super();
}
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Retrieve the name of the directory where this script is stored.
*
* @return the directory name
*/
public String dirName()
{
StringBuffer dir = userTemplateDirName(user());
return dir.toString();
}
// ----------------------------------------------------------
/**
* Retrieve the path name for this report template.
*
* @return the path to the template
*/
public String filePath()
{
return dirName() + "/" + id().toString() + TEMPLATE_EXTENSION;
}
// ----------------------------------------------------------
public String toString()
{
return filePath();
}
// ----------------------------------------------------------
/**
* Retrieve the name of the directory where a user's report templates are
* stored.
*
* @param author
* the user
* @return the directory name
*/
public static StringBuffer userTemplateDirName(User author)
{
StringBuffer dir = new StringBuffer(50);
dir.append(templateRoot());
if (author != null)
{
dir.append('/');
dir.append(author.authenticationDomain().subdirName());
dir.append('/');
dir.append(author.userName());
}
return dir;
}
// ----------------------------------------------------------
/**
* Retrieve the name of the directory where all user report templates are
* stored.
*
* @return the directory name
*/
public static String templateRoot()
{
if (templateRoot == null)
{
templateRoot = org.webcat.core.Application
.configurationProperties().getProperty(
"grader.reporttemplatesroot");
if (templateRoot == null)
{
templateRoot = org.webcat.core.Application
.configurationProperties().getProperty(
"grader.submissiondir")
+ "/UserReportTemplates";
}
}
return templateRoot;
}
// ----------------------------------------------------------
/**
* Gets the report template that represents the next version of this
* template.
*
* @return the next version of the report template
*/
public ReportTemplate successorTemplate()
{
@SuppressWarnings("unchecked")
NSArray<ReportTemplate> successors = (NSArray<ReportTemplate>)
storedValueForKey("successorTemplateArray");
if (successors == null || successors.count() == 0)
{
return null;
}
else
{
return successors.objectAtIndex(0);
}
}
// ----------------------------------------------------------
@Override
public MutableArray parameters()
{
// Older reports will have a null parameter set, so to ensure clean
// code elsewhere we just return an empty array for these.
MutableArray params = super.parameters();
if (params == null)
{
return new MutableArray();
}
else
{
return params;
}
}
// ----------------------------------------------------------
/**
* Gets a set that contains the visual design elements that are contained in
* this report template.
*
* This is the preferred method to use to access this property, rather than
* the raw string returned by {@link designElementsRaw}.
*
* @return an {@link NSSet} that contains the kinds of design elements in
* the report template
*/
public NSSet<String> designElements()
{
NSMutableSet<String> set = new NSMutableSet<String>();
String raw = designElementsRaw();
if (raw != null)
{
String[] elements = designElementsRaw().split(" ");
for (String element : elements)
{
set.addObject(element);
}
}
return set;
}
// ----------------------------------------------------------
/**
* Sets the visual design elements that are contained in this report
* template.
*
* This is the preferred method to use to access this property, rather than
* passing a raw string into {@link setDesignElementsRaw}.
*
* @param set
* an {@link NSSet} that contains the kinds of design elements in
* the report template
*/
public void setDesignElements(NSSet<String> set)
{
StringBuffer buffer = new StringBuffer(32);
if (set.count() > 0)
{
boolean first = true;
for (String element : set)
{
if (first)
first = false;
else
buffer.append(' ');
buffer.append(element);
}
}
setDesignElementsRaw(buffer.toString());
}
// ----------------------------------------------------------
/**
* Create a new report template object from uploaded file data.
*
* @param ec
* the editing context in which to add the new object
* @param owner
* the user uploading the template
* @param uploadedName
* the template's file name
* @param uploadedData
* the file's data
* @param errors
* a dictionary in which to store any error messages for display
* to the user
* @return the new report template, if successful, or null if unsuccessful
*/
public static ReportTemplate createNewReportTemplate(EOEditingContext ec,
User owner, String uploadedName, NSData uploadedData,
NSMutableDictionary<?, ?> errors)
{
ReportTemplate template = new ReportTemplate();
ec.insertObject(template);
template.setName("");
ec.saveChanges();
template.setUploadedTime(new NSTimestamp());
template.setUserRelationship(owner);
// Save the file to disk
log.debug("Saving report template to disk: " + template.filePath());
File templateFile = new File(template.filePath());
try
{
templateFile.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(templateFile);
uploadedData.writeToStream(out);
out.close();
}
catch (java.io.IOException e)
{
log.error("Error saving report template to disk:", e);
String msg = e.getMessage();
errors.setObjectForKey(msg, msg);
ec.deleteObject(template);
templateFile.delete();
ec.saveChanges();
return null;
}
// Migrate the report template to the new extension points if
// needed.
template.migrateTemplate();
SessionHandle designSession = Reporter.getInstance().designSession();
ReportDesignHandle reportHandle = null;
try
{
reportHandle = designSession.openDesign(template.filePath());
String msg = template.processMetadata(reportHandle);
if (msg != null)
{
log.error("Error processing report template metadata: " + msg);
errors.setObjectForKey(msg, msg);
ec.deleteObject(template);
templateFile.delete();
ec.saveChanges();
return null;
}
msg = template.visitReportParameters(reportHandle);
if (msg != null)
{
log.error("Error processing report parameters: " + msg);
errors.setObjectForKey(msg, msg);
ec.deleteObject(template);
templateFile.delete();
ec.saveChanges();
return null;
}
msg = template.deeplyVisitTemplate(ec, reportHandle);
if (msg != null)
{
log.error("Error walking report template: " + msg);
errors.setObjectForKey(msg, msg);
ec.deleteObject(template);
templateFile.delete();
ec.saveChanges();
return null;
}
// Save any changes that we have made to the report template up to
// this point (mainly, the data set IDs in the query text).
reportHandle.save();
return template;
}
catch (Exception e)
{
log.error("Error opening report template:", e);
String msg = "There was an internal error opening the report template: "
+ e.toString();
errors.setObjectForKey(msg, msg);
ec.deleteObject(template);
templateFile.delete();
ec.saveChanges();
return null;
}
finally
{
if (reportHandle != null)
{
reportHandle.close();
}
}
}
// ----------------------------------------------------------
/**
* After a report template has been uploaded and all of its properties set,
* call this method to update the repository metadata properties in the
* template and then compute its checksum.
*
* @param idProvider
* the object used to generate repository IDs for this template
*/
public void updateRepositoryMetadataAndFinalize(
IRepositoryIdProvider idProvider)
{
SessionHandle designSession = Reporter.getInstance().designSession();
ReportDesignHandle reportHandle = null;
try
{
reportHandle = designSession.openDesign(filePath());
DateFormat dateFmt = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.FULL);
String repositoryId = idProvider.idForReportTemplate(this);
String rootId = idProvider.idForReportTemplate(rootTemplate());
ReportMetadata.setRepositoryId(reportHandle, repositoryId);
ReportMetadata.setRepositoryRootId(reportHandle, rootId);
ReportMetadata.setRepositoryVersion(reportHandle, version());
ReportMetadata.setRepositoryUploadDate(reportHandle, dateFmt
.format(uploadedTime()));
ReportMetadata.setRepositoryChangeHistory(reportHandle,
changeHistory());
// Save any changes that we have made back to the file.
reportHandle.save();
}
catch (Exception e)
{
log.error("Exception occurred trying to update report template "
+ "metadata: ", e);
}
// Compute the MD5 checksum of the file contents. This must be the
// last thing we do, because setting the repository properties
// (and other modifications) will change the contents of the file.
setChecksum(ChecksumUtils.checksumFromContentsOfFile(
new File(filePath())));
}
// ----------------------------------------------------------
/**
* Performs any updates to the report template that might be necessary
* after changes have been made to the Web-CAT server.
*/
public void migrateTemplate()
{
// It would be nice if we could use the report designer APIs for this,
// but it turns out trying to set the extensionID of a data source or
// data set silently fails, so we have to use the caveman approach
// instead.
/*String templateContents =
FileUtilities.stringWithContentsOfFile(filePath());
if (templateContents != null)
{
Matcher matcher =
packageNameMigrationPattern.matcher(templateContents);
if (matcher.find())
{
templateContents = matcher.replaceAll("extensionID=\"org.webcat");
log.info("Migrating template " + filePath()
+ " to org.webcat.* package names");
FileUtilities.writeStringToFile(templateContents, filePath());
}
}
else
{
log.error("Could not load template file contents for migration: "
+ filePath());
}*/
}
// ----------------------------------------------------------
/**
* Gets the array of all report templates that are accessible by the
* specified user. This is the union of that user's own uploaded templates
* and all of the published templates.
*
* @param ec the editing context to load the templates into
* @param forUser the user
*
* @return the array of report templates accessible by the user
*/
public static NSArray<ReportTemplate> templatesAccessibleByUser(
EOEditingContext ec, User forUser)
{
// Admins have access to everything, so just short-circuit this.
if (forUser.hasAdminPrivileges())
{
return allTemplatesOrderedByName(ec);
}
NSArray<ReportTemplate> userTemplates = templatesForUser(ec, forUser);
NSArray<ReportTemplate> publishedTemplates = publishedTemplates(ec);
NSMutableArray<ReportTemplate> allTemplates =
new NSMutableArray<ReportTemplate>();
allTemplates.addObjectsFromArray(userTemplates);
ERXArrayUtilities.addObjectsFromArrayWithoutDuplicates(allTemplates,
publishedTemplates);
ERXArrayUtilities.sortArrayWithKey(allTemplates,
ReportTemplate.NAME_KEY);
return allTemplates;
}
// ----------------------------------------------------------
/**
* Initializes various report template attributes in the EO model.
*/
private String processMetadata(ReportDesignHandle reportHandle)
{
// Set the title and description of the report.
String title = ReportMetadata.getTitle(reportHandle);
@SuppressWarnings("hiding")
String description = ReportMetadata.getDescription(reportHandle);
if (title == null || title.trim().length() == 0)
{
String msg = "The report template you tried to upload does not "
+ "have a title. Please enter one in the <b>Title</b> field "
+ "on the Overview page of the report in the report designer "
+ "and then upload it again.";
return msg;
}
if (description == null || description.trim().length() == 0)
{
String msg = "The report template you tried to upload does not "
+ "have a description. Please enter one in the "
+ "<b>Description</b> field on the Overview page of the "
+ "report in the report designer and then upload it again.";
return msg;
}
setName(title);
setDescription(description);
// Set the language identifier of the template.
@SuppressWarnings("hiding")
String language = ReportMetadata.getLanguage(reportHandle);
if (language == null)
{
language = "en";
}
setLanguage(language);
// Set the preferred renderer of the template.
String renderer = ReportMetadata.getPreferredRenderer(reportHandle);
if (renderer == null)
{
renderer = "html";
}
setPreferredRenderer(renderer);
return null;
}
// ----------------------------------------------------------
private String visitReportParameters(ReportDesignHandle reportHandle)
{
ReportParameterVisitor visitor = new ReportParameterVisitor();
visitor.apply(reportHandle);
setParameters(new MutableArray(visitor.parameterGroups()));
return null;
}
// ----------------------------------------------------------
/**
* Collects information about the report template by performing a deep
* visitation of its layout.
*
* @param ec
* the editing context in which to work
* @param reportHandle
* the BIRT report design handle
*
* @return a String indicating any errors that occurred, or null if it was
* successful
*/
private String deeplyVisitTemplate(EOEditingContext ec,
ReportDesignHandle reportHandle)
{
UploadedTemplateVisitor visitor = new UploadedTemplateVisitor();
visitor.apply(reportHandle);
NSDictionary<DataSetHandle, Integer> sets = visitor
.dataSetsAndRefCounts();
for (DataSetHandle dataSetHandle : sets.allKeys())
{
int refCount = sets.objectForKey(dataSetHandle);
@SuppressWarnings("hiding")
String name = DataSetMetadata.getName(dataSetHandle);
@SuppressWarnings("hiding")
String description = DataSetMetadata.getDescription(dataSetHandle);
String queryText = dataSetHandle.getStringProperty("queryText");
DataSetDescription relation = new DataSetDescription(queryText);
ReportDataSet dataSet = ReportDataSet
.createNewReportDataSet(ec, this, relation.getEntityType(),
name, description, refCount);
String realId = dataSet.id().toString();
relation.setUniqueId(realId);
try
{
dataSetHandle.setStringProperty("queryText", relation
.getQueryText());
}
catch (SemanticException e)
{
return "An exception occurred when manipulating the data set "
+ "query text in the report template: "
+ e.getMessage();
}
}
// Set the design elements that were found by the visitor.
setDesignElements(visitor.reportElements());
return null;
}
//~ Instance/static variables .............................................
public static final String TEMPLATE_EXTENSION = ".rptdesign";
/*
* Constants that define the valid "kinds" of report elements that are
* catalogued when a report template is uploaded.
*/
public static final String ELEMENT_TABLE = "table";
public static final String ELEMENT_CHART = "chart";
public static final String ELEMENT_CROSSTAB = "crosstab";
static private String templateRoot = null;
static Logger log = Logger.getLogger(ReportTemplate.class);
}