/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program 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.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.sys.document.web;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.Tag;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition;
import org.kuali.kfs.sys.document.datadictionary.TotalDefinition;
import org.kuali.kfs.sys.document.web.renderers.CellCountCurious;
import org.kuali.kfs.sys.document.web.renderers.CollectionPropertiesCurious;
import org.kuali.kfs.sys.document.web.renderers.GroupErrorsRenderer;
import org.kuali.kfs.sys.document.web.renderers.GroupTitleLineRenderer;
import org.kuali.kfs.sys.document.web.renderers.Renderer;
import org.kuali.kfs.sys.document.web.renderers.RepresentedCellCurious;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.krad.util.GlobalVariables;
/**
* This represents an accounting line group in renderable state
*/
public class DefaultAccountingLineGroupImpl implements AccountingLineGroup {
protected AccountingLineGroupDefinition groupDefinition;
protected JspFragment importLineOverride;
protected String collectionPropertyName;
protected List<? extends AccountingLineRenderingContext> containers;
protected AccountingDocument accountingDocument;
protected int cellCount = 0;
protected int arbitrarilyHighIndex;
protected Map<String, Object> displayedErrors;
protected Map<String, Object> displayedWarnings;
protected Map<String, Object> displayedInfo;
protected boolean canEdit;
protected String collectionItemPropertyName;
protected List errorKeys;
/**
* Constructs a DefaultAccountingLineGroupImpl
*/
public DefaultAccountingLineGroupImpl() {}
/**
* Initializes the DefaultAccountingLineGroupImpl
*
* @param groupDefinition the data dictionary group definition for this accounting line group
* @param accountingDocument the document which owns or will own the accounting line being rendered
* @param containers the containers within this group
* @param collectionPropertyName the property name of the collection of accounting lines owned by this group
* @param errors a List of errors keys for errors on the page
* @param displayedErrors a Map of errors that have already been displayed
* @param canEdit determines if the page can be edited or not
*/
public void initialize(AccountingLineGroupDefinition groupDefinition, AccountingDocument accountingDocument, List<RenderableAccountingLineContainer> containers, String collectionPropertyName, String collectionItemPropertyName, Map<String, Object> displayedErrors, Map<String, Object> displayedWarnings, Map<String, Object> displayedInfo, boolean canEdit) {
this.groupDefinition = groupDefinition;
this.accountingDocument = accountingDocument;
this.containers = containers;
this.collectionPropertyName = collectionPropertyName;
this.collectionItemPropertyName = collectionItemPropertyName;
this.displayedErrors = displayedErrors;
this.displayedWarnings = displayedWarnings;
this.displayedInfo = displayedInfo;
this.canEdit = canEdit;
}
/**
* Renders the whole of this accounting line group
*
* @param pageContext the page context to render to
* @param parentTag the AccountingLinesTag that is requesting this rendering
*/
@Override
public void renderEverything(PageContext pageContext, Tag parentTag) throws JspException {
if (groupDefinition.isHeaderRendering()) {
renderGroupHeader(pageContext, parentTag);
}
renderAccountingLineContainers(pageContext, parentTag);
if (shouldRenderTotals()) {
renderTotals(pageContext, parentTag);
}
}
/**
* Checks that there are either source or target accounting lines available and that at least one totals definition has been set up for this accounting line group
* @see org.kuali.kfs.sys.document.web.AccountingLineGroup#shouldRenderTotals()
*/
@Override
public boolean shouldRenderTotals() {
boolean renderTotals = !accountingDocument.getSourceAccountingLines().isEmpty() || !accountingDocument.getTargetAccountingLines().isEmpty();
renderTotals &= groupDefinition.getTotals() != null && groupDefinition.getTotals().size() > 0;
return renderTotals;
}
/**
* Finds the maximum number of cells in the accounting line table row
*
* @param rows the rows which are being rendered
* @return the maximum number of cells to render
*/
@Override
public int getWidthInCells() {
if (groupDefinition.getForceColumnCount() > 0) {
return groupDefinition.getForceColumnCount();
}
if (cellCount > 0) {
return cellCount;
}
int max = 0;
for (AccountingLineRenderingContext line : containers) {
if (line.getRenderableCellCount() > max) {
max = line.getRenderableCellCount();
}
}
cellCount = max;
return cellCount;
}
/**
* Renders the group header/import line for the accounting line group. Renders importLineOverride if present; otherwise, uses
* ImportLineRenderer to do its dirty work
*
* @param accountingLineGroupDefinition the accounting line group definition
* @param rows the rows to render
* @throws JspException thrown if something goes wrong in rendering the header
*/
protected void renderGroupHeader(PageContext pageContext, Tag parentTag) throws JspException {
if (importLineOverride != null) {
try {
importLineOverride.invoke(pageContext.getOut());
}
catch (IOException ioe) {
throw new JspException("Could not render import line override fragment", ioe);
}
}
else {
GroupTitleLineRenderer groupTitleLineRenderer = new GroupTitleLineRenderer();
groupTitleLineRenderer.setAccountingLineGroupDefinition(groupDefinition);
groupTitleLineRenderer.setCellCount(getWidthInCells());
groupTitleLineRenderer.setLineCollectionProperty(collectionPropertyName);
groupTitleLineRenderer.setAccountingDocument(accountingDocument);
groupTitleLineRenderer.setCanEdit(canEdit);
boolean isGroupEditable = groupDefinition.getAccountingLineAuthorizer().isGroupEditable(accountingDocument, containers, GlobalVariables.getUserSession().getPerson());
groupTitleLineRenderer.overrideCanUpload(groupDefinition.isImportingAllowed() && isGroupEditable);
groupTitleLineRenderer.setGroupActionsRendered(!this.isDocumentEnrouted() && isGroupEditable);
groupTitleLineRenderer.render(pageContext, parentTag);
groupTitleLineRenderer.clear();
}
renderErrors(pageContext, parentTag);
}
/**
* Renders any errors for the group
* @param pageContext the page context where the errors will be rendered on
* @param parentTag the parent tag requesting the rendering
*/
protected void renderErrors(PageContext pageContext, Tag parentTag) throws JspException {
GroupErrorsRenderer errorRenderer = getErrorRenderer();
errorRenderer.setErrorKeyMatch(groupDefinition.getErrorKey());
errorRenderer.setColSpan(getWidthInCells());
errorRenderer.render(pageContext, parentTag);
moveListToMap(errorRenderer.getErrorsRendered(), getDisplayedErrors());
moveListToMap(errorRenderer.getWarningsRendered(), getDisplayedWarnings());
moveListToMap(errorRenderer.getInfoRendered(), getDisplayedInfo());
errorRenderer.clear();
}
/**
* Moves all of the members of theList into theMap as a key with the value always being the String "true"
* @param theList the List of Strings to be keys
* @param theMap the Map of keys and values
*/
protected void moveListToMap(List<String> theList, Map theMap) {
for (String s : theList) {
theMap.put(s, "true");
}
}
/**
* @return get a GroupErrorsRenderer in a way which can be overridden
*/
protected GroupErrorsRenderer getErrorRenderer() {
return new GroupErrorsRenderer();
}
/**
* Renders the accounting line containers
*
* @param containers the containers to render
* @throws JspException thrown if rendering goes badly
*/
protected void renderAccountingLineContainers(PageContext pageContext, Tag parentTag) throws JspException {
for (AccountingLineRenderingContext container : containers) {
container.populateValuesForFields();
container.populateWithTabIndexIfRequested(arbitrarilyHighIndex);
container.renderElement(pageContext, parentTag, container);
}
}
/**
* Renders all of the totals required by the group total definition
*
* @param groupDefinition the accounting line view group definition
* @param lines the lines that will be rendered - so we can count how many cells we're rendering
* @throws JspException thrown if something goes wrong
*/
protected void renderTotals(PageContext pageContext, Tag parentTag) throws JspException {
int cellCount = getWidthInCells();
List<? extends TotalDefinition> groupTotals = groupDefinition.getTotals();
for (TotalDefinition definition : groupTotals) {
if (definition instanceof NestedFieldTotaling) {
NestedFieldTotaling nestedFieldTotaling = definition;
if (nestedFieldTotaling.isNestedProperty()) {
int index = groupTotals.indexOf(definition);
AccountingLineRenderingContext container = this.containers.get(index);
String containingObjectPropertyName = container.getAccountingLineContainingObjectPropertyName();
nestedFieldTotaling.setContainingPropertyName(containingObjectPropertyName);
}
}
Renderer renderer = definition.getTotalRenderer();
if (renderer instanceof CellCountCurious) {
((CellCountCurious) renderer).setCellCount(cellCount);
}
if (renderer instanceof RepresentedCellCurious) {
RepresentedCellCurious representedCellCurious = ((RepresentedCellCurious) renderer);
int columnNumberOfRepresentedCell = this.getRepresentedColumnNumber(representedCellCurious.getRepresentedCellPropertyName());
representedCellCurious.setColumnNumberOfRepresentedCell(columnNumberOfRepresentedCell);
}
if (renderer instanceof CollectionPropertiesCurious) {
((CollectionPropertiesCurious)renderer).setCollectionProperty(this.collectionPropertyName);
((CollectionPropertiesCurious)renderer).setCollectionItemProperty(this.collectionItemPropertyName);
}
renderer.render(pageContext, parentTag);
renderer.clear();
}
}
/**
* get the column number of the tabel cell with the given property name in an accounting line table
*
* @param propertyName the given property name that is associated with the column
* @return the column number of the tabel cell with the given property name in an accounting line table
*/
protected int getRepresentedColumnNumber(String propertyName) {
for (AccountingLineRenderingContext container : containers) {
List<AccountingLineTableRow> tableRows = container.getRows();
for (AccountingLineTableRow row : tableRows) {
List<AccountingLineTableCell> tableCells = row.getCells();
int cumulativeDisplayCellCount = 0;
for (AccountingLineTableCell cell : tableCells) {
cumulativeDisplayCellCount += cell.getColSpan();
List<RenderableElement> fields = cell.getRenderableElement();
for (RenderableElement field : fields) {
if (field instanceof ElementNamable == false) {
continue;
}
if (((ElementNamable) field).getName().equals(propertyName)) {
return cumulativeDisplayCellCount;
}
}
}
}
}
return -1;
}
/**
* Sets the cellCount attribute value.
*
* @param cellCount The cellCount to set.
*/
@Override
public void setCellCount(int cellCount) {
this.cellCount = cellCount;
}
/**
* Sets the importLineOverride attribute value.
*
* @param importLineOverride The importLineOverride to set.
*/
@Override
public void setImportLineOverride(JspFragment importLineOverride) {
this.importLineOverride = importLineOverride;
}
/**
* Sets the form's arbitrarily high tab index
*
* @param arbitrarilyHighIndex the index to set
*/
@Override
public void setArbitrarilyHighIndex(int arbitrarilyHighIndex) {
this.arbitrarilyHighIndex = arbitrarilyHighIndex;
}
/**
* Gets the displayedWarnings attribute.
* @return Returns the displayedWarnings.
*/
public Map getDisplayedWarnings() {
return displayedWarnings;
}
/**
* Gets the displayedInfo attribute.
* @return Returns the displayedInfo.
*/
public Map getDisplayedInfo() {
return displayedInfo;
}
/**
* Gets the errorKeys attribute.
* @return Returns the errorKeys.
*/
@Override
public List getErrorKeys() {
return errorKeys;
}
/**
* Sets the errorKeys attribute value.
* @param errorKeys The errorKeys to set.
*/
@Override
public void setErrorKeys(List errorKeys) {
this.errorKeys = errorKeys;
}
/**
* Determines if the current document is enrouted
*/
private boolean isDocumentEnrouted() {
WorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument();
return !workflowDocument.isInitiated() && !workflowDocument.isSaved();
}
/**
* Determines if there is more than one editable line in this group; if so, then it allows deleting
*/
@Override
public void updateDeletabilityOfAllLines() {
if (this.isDocumentEnrouted()) {
if (hasEnoughAccountingLinesForDelete()) {
for (AccountingLineRenderingContext accountingLineRenderingContext : containers) {
if (accountingLineRenderingContext.isEditableLine()) {
accountingLineRenderingContext.makeDeletable();
}
}
}
} else {
// we're pre-route - everybody is deletable!
for (AccountingLineRenderingContext accountingLineRenderingContext : containers) {
accountingLineRenderingContext.makeDeletable();
}
}
}
/**
* Determines if there are enough accounting lines in this group for delete buttons to be present
* @return true if there are enough accounting lines for a delete, false otherwise
*/
protected boolean hasEnoughAccountingLinesForDelete() {
// 1. get the count of how many accounting lines are editable
int editableLineCount = 0;
for (AccountingLineRenderingContext accountingLineRenderingContext : containers) {
if (!accountingLineRenderingContext.isNewLine() && accountingLineRenderingContext.isEditableLine()) {
editableLineCount += 1;
}
if (editableLineCount == 2)
{
return true; // we know we're good...skip out early
}
}
return false;
}
/**
* Gets the collectionItemPropertyName attribute.
* @return Returns the collectionItemPropertyName.
*/
@Override
public String getCollectionItemPropertyName() {
return collectionItemPropertyName;
}
/**
* Gets the groupDefinition attribute.
* @return Returns the groupDefinition.
*/
public AccountingLineGroupDefinition getGroupDefinition() {
return groupDefinition;
}
/**
* Sets the groupDefinition attribute value.
* @param groupDefinition The groupDefinition to set.
*/
public void setGroupDefinition(AccountingLineGroupDefinition groupDefinition) {
this.groupDefinition = groupDefinition;
}
/**
* Gets the displayedErrors attribute.
* @return Returns the displayedErrors.
*/
public Map getDisplayedErrors() {
return displayedErrors;
}
/**
* Sets the displayedErrors attribute value.
* @param displayedErrors The displayedErrors to set.
*/
public void setDisplayedErrors(Map displayedErrors) {
this.displayedErrors = displayedErrors;
}
/**
* Gets the collectionPropertyName attribute.
* @return Returns the collectionPropertyName.
*/
public String getCollectionPropertyName() {
return collectionPropertyName;
}
/**
* Sets the collectionPropertyName attribute value.
* @param collectionPropertyName The collectionPropertyName to set.
*/
public void setCollectionPropertyName(String collectionPropertyName) {
this.collectionPropertyName = collectionPropertyName;
}
/**
* Sets the collectionItemPropertyName attribute value.
* @param collectionItemPropertyName The collectionItemPropertyName to set.
*/
public void setCollectionItemPropertyName(String collectionItemPropertyName) {
this.collectionItemPropertyName = collectionItemPropertyName;
}
public AccountingDocument getAccountingDocument() {
return accountingDocument;
}
public void setAccountingDocument(AccountingDocument accountingDocument) {
this.accountingDocument = accountingDocument;
}
}