001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.sys.document.web; 017 018 import java.io.IOException; 019 import java.util.List; 020 import java.util.Map; 021 022 import javax.servlet.jsp.JspException; 023 import javax.servlet.jsp.PageContext; 024 import javax.servlet.jsp.tagext.JspFragment; 025 import javax.servlet.jsp.tagext.Tag; 026 027 import org.kuali.kfs.sys.document.AccountingDocument; 028 import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition; 029 import org.kuali.kfs.sys.document.datadictionary.TotalDefinition; 030 import org.kuali.kfs.sys.document.web.renderers.CellCountCurious; 031 import org.kuali.kfs.sys.document.web.renderers.CollectionPropertiesCurious; 032 import org.kuali.kfs.sys.document.web.renderers.GroupErrorsRenderer; 033 import org.kuali.kfs.sys.document.web.renderers.GroupTitleLineRenderer; 034 import org.kuali.kfs.sys.document.web.renderers.Renderer; 035 import org.kuali.kfs.sys.document.web.renderers.RepresentedCellCurious; 036 import org.kuali.rice.kns.util.GlobalVariables; 037 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument; 038 039 /** 040 * This represents an accounting line group in renderable state 041 */ 042 public class DefaultAccountingLineGroupImpl implements AccountingLineGroup { 043 protected AccountingLineGroupDefinition groupDefinition; 044 protected JspFragment importLineOverride; 045 protected String collectionPropertyName; 046 protected List<RenderableAccountingLineContainer> containers; 047 protected AccountingDocument accountingDocument; 048 protected int cellCount = 0; 049 protected int arbitrarilyHighIndex; 050 protected Map<String, Object> displayedErrors; 051 protected Map<String, Object> displayedWarnings; 052 protected Map<String, Object> displayedInfo; 053 protected boolean canEdit; 054 protected String collectionItemPropertyName; 055 protected List errorKeys; 056 057 /** 058 * Constructs a DefaultAccountingLineGroupImpl 059 */ 060 public DefaultAccountingLineGroupImpl() {} 061 062 /** 063 * Initializes the DefaultAccountingLineGroupImpl 064 * 065 * @param groupDefinition the data dictionary group definition for this accounting line group 066 * @param accountingDocument the document which owns or will own the accounting line being rendered 067 * @param containers the containers within this group 068 * @param collectionPropertyName the property name of the collection of accounting lines owned by this group 069 * @param errors a List of errors keys for errors on the page 070 * @param displayedErrors a Map of errors that have already been displayed 071 * @param canEdit determines if the page can be edited or not 072 */ 073 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) { 074 this.groupDefinition = groupDefinition; 075 this.accountingDocument = accountingDocument; 076 this.containers = containers; 077 this.collectionPropertyName = collectionPropertyName; 078 this.collectionItemPropertyName = collectionItemPropertyName; 079 this.displayedErrors = displayedErrors; 080 this.displayedWarnings = displayedWarnings; 081 this.displayedInfo = displayedInfo; 082 this.canEdit = canEdit; 083 } 084 085 /** 086 * Renders the whole of this accounting line group 087 * 088 * @param pageContext the page context to render to 089 * @param parentTag the AccountingLinesTag that is requesting this rendering 090 */ 091 public void renderEverything(PageContext pageContext, Tag parentTag) throws JspException { 092 if (groupDefinition.isHeaderRendering()) { 093 renderGroupHeader(pageContext, parentTag); 094 } 095 renderAccountingLineContainers(pageContext, parentTag); 096 097 boolean renderTotals = !accountingDocument.getSourceAccountingLines().isEmpty() || !accountingDocument.getTargetAccountingLines().isEmpty(); 098 renderTotals &= groupDefinition.getTotals() != null && groupDefinition.getTotals().size() > 0; 099 if (renderTotals) { 100 renderTotals(pageContext, parentTag); 101 } 102 } 103 104 /** 105 * Finds the maximum number of cells in the accounting line table row 106 * 107 * @param rows the rows which are being rendered 108 * @return the maximum number of cells to render 109 */ 110 public int getWidthInCells() { 111 if (groupDefinition.getForceColumnCount() > 0) 112 return groupDefinition.getForceColumnCount(); 113 if (cellCount > 0) 114 return cellCount; 115 116 int max = 0; 117 for (RenderableAccountingLineContainer line : containers) { 118 if (line.getCellCount() > max) { 119 max = line.getCellCount(); 120 } 121 } 122 cellCount = max; 123 return cellCount; 124 } 125 126 /** 127 * Renders the group header/import line for the accounting line group. Renders importLineOverride if present; otherwise, uses 128 * ImportLineRenderer to do its dirty work 129 * 130 * @param accountingLineGroupDefinition the accounting line group definition 131 * @param rows the rows to render 132 * @throws JspException thrown if something goes wrong in rendering the header 133 */ 134 protected void renderGroupHeader(PageContext pageContext, Tag parentTag) throws JspException { 135 if (importLineOverride != null) { 136 try { 137 importLineOverride.invoke(pageContext.getOut()); 138 } 139 catch (IOException ioe) { 140 throw new JspException("Could not render import line override fragment", ioe); 141 } 142 } 143 else { 144 GroupTitleLineRenderer groupTitleLineRenderer = new GroupTitleLineRenderer(); 145 groupTitleLineRenderer.setAccountingLineGroupDefinition(groupDefinition); 146 groupTitleLineRenderer.setCellCount(getWidthInCells()); 147 groupTitleLineRenderer.setLineCollectionProperty(collectionPropertyName); 148 groupTitleLineRenderer.setAccountingDocument(accountingDocument); 149 groupTitleLineRenderer.setCanEdit(canEdit); 150 151 boolean isGroupEditable = groupDefinition.getAccountingLineAuthorizer().isGroupEditable(accountingDocument, containers, GlobalVariables.getUserSession().getPerson()); 152 groupTitleLineRenderer.overrideCanUpload(groupDefinition.isImportingAllowed() && isGroupEditable); 153 groupTitleLineRenderer.setGroupActionsRendered(!this.isDocumentEnrouted() && isGroupEditable); 154 155 groupTitleLineRenderer.render(pageContext, parentTag); 156 groupTitleLineRenderer.clear(); 157 } 158 159 renderErrors(pageContext, parentTag); 160 } 161 162 /** 163 * Renders any errors for the group 164 * @param pageContext the page context where the errors will be rendered on 165 * @param parentTag the parent tag requesting the rendering 166 */ 167 protected void renderErrors(PageContext pageContext, Tag parentTag) throws JspException { 168 GroupErrorsRenderer errorRenderer = getErrorRenderer(); 169 errorRenderer.setErrorKeyMatch(groupDefinition.getErrorKey()); 170 errorRenderer.setColSpan(getWidthInCells()); 171 errorRenderer.render(pageContext, parentTag); 172 173 moveListToMap(errorRenderer.getErrorsRendered(), getDisplayedErrors()); 174 moveListToMap(errorRenderer.getWarningsRendered(), getDisplayedWarnings()); 175 moveListToMap(errorRenderer.getInfoRendered(), getDisplayedInfo()); 176 177 errorRenderer.clear(); 178 } 179 180 /** 181 * Moves all of the members of theList into theMap as a key with the value always being the String "true" 182 * @param theList the List of Strings to be keys 183 * @param theMap the Map of keys and values 184 */ 185 protected void moveListToMap(List<String> theList, Map theMap) { 186 for (String s : theList) { 187 theMap.put(s, "true"); 188 } 189 } 190 191 /** 192 * @return get a GroupErrorsRenderer in a way which can be overridden 193 */ 194 protected GroupErrorsRenderer getErrorRenderer() { 195 return new GroupErrorsRenderer(); 196 } 197 198 /** 199 * Renders the accounting line containers 200 * 201 * @param containers the containers to render 202 * @throws JspException thrown if rendering goes badly 203 */ 204 protected void renderAccountingLineContainers(PageContext pageContext, Tag parentTag) throws JspException { 205 for (RenderableAccountingLineContainer container : containers) { 206 container.populateValuesForFields(); 207 container.populateWithTabIndexIfRequested(arbitrarilyHighIndex); 208 container.renderElement(pageContext, parentTag, container); 209 } 210 } 211 212 /** 213 * Renders all of the totals required by the group total definition 214 * 215 * @param groupDefinition the accounting line view group definition 216 * @param lines the lines that will be rendered - so we can count how many cells we're rendering 217 * @throws JspException thrown if something goes wrong 218 */ 219 protected void renderTotals(PageContext pageContext, Tag parentTag) throws JspException { 220 int cellCount = getWidthInCells(); 221 222 List<? extends TotalDefinition> groupTotals = groupDefinition.getTotals(); 223 for (TotalDefinition definition : groupTotals) { 224 if (definition instanceof NestedFieldTotaling) { 225 NestedFieldTotaling nestedFieldTotaling = (NestedFieldTotaling) definition; 226 227 if (nestedFieldTotaling.isNestedProperty()) { 228 int index = groupTotals.indexOf(definition); 229 RenderableAccountingLineContainer container = this.containers.get(index); 230 String containingObjectPropertyName = container.getAccountingLineContainingObjectPropertyName(); 231 nestedFieldTotaling.setContainingPropertyName(containingObjectPropertyName); 232 } 233 } 234 235 Renderer renderer = definition.getTotalRenderer(); 236 if (renderer instanceof CellCountCurious) { 237 ((CellCountCurious) renderer).setCellCount(cellCount); 238 } 239 240 if (renderer instanceof RepresentedCellCurious) { 241 RepresentedCellCurious representedCellCurious = ((RepresentedCellCurious) renderer); 242 int columnNumberOfRepresentedCell = this.getRepresentedColumnNumber(representedCellCurious.getRepresentedCellPropertyName()); 243 representedCellCurious.setColumnNumberOfRepresentedCell(columnNumberOfRepresentedCell); 244 } 245 246 if (renderer instanceof CollectionPropertiesCurious) { 247 ((CollectionPropertiesCurious)renderer).setCollectionProperty(this.collectionPropertyName); 248 ((CollectionPropertiesCurious)renderer).setCollectionItemProperty(this.collectionItemPropertyName); 249 } 250 251 renderer.render(pageContext, parentTag); 252 renderer.clear(); 253 } 254 } 255 256 /** 257 * get the column number of the tabel cell with the given property name in an accounting line table 258 * 259 * @param propertyName the given property name that is associated with the column 260 * @return the column number of the tabel cell with the given property name in an accounting line table 261 */ 262 protected int getRepresentedColumnNumber(String propertyName) { 263 for (RenderableAccountingLineContainer container : containers) { 264 List<AccountingLineTableRow> tableRows = container.getRows(); 265 266 for (AccountingLineTableRow row : tableRows) { 267 List<AccountingLineTableCell> tableCells = row.getCells(); 268 269 for (AccountingLineTableCell cell : tableCells) { 270 List<RenderableElement> fields = cell.getRenderableElement(); 271 272 for (RenderableElement field : fields) { 273 if (field instanceof ElementNamable == false) { 274 continue; 275 } 276 277 if (((ElementNamable) field).getName().equals(propertyName)) { 278 return tableCells.indexOf(cell) + 1; 279 } 280 } 281 } 282 } 283 } 284 285 return -1; 286 } 287 288 /** 289 * Sets the cellCount attribute value. 290 * 291 * @param cellCount The cellCount to set. 292 */ 293 public void setCellCount(int cellCount) { 294 this.cellCount = cellCount; 295 } 296 297 /** 298 * Sets the importLineOverride attribute value. 299 * 300 * @param importLineOverride The importLineOverride to set. 301 */ 302 public void setImportLineOverride(JspFragment importLineOverride) { 303 this.importLineOverride = importLineOverride; 304 } 305 306 /** 307 * Sets the form's arbitrarily high tab index 308 * 309 * @param arbitrarilyHighIndex the index to set 310 */ 311 public void setArbitrarilyHighIndex(int arbitrarilyHighIndex) { 312 this.arbitrarilyHighIndex = arbitrarilyHighIndex; 313 } 314 315 /** 316 * Gets the displayedWarnings attribute. 317 * @return Returns the displayedWarnings. 318 */ 319 public Map getDisplayedWarnings() { 320 return displayedWarnings; 321 } 322 323 /** 324 * Gets the displayedInfo attribute. 325 * @return Returns the displayedInfo. 326 */ 327 public Map getDisplayedInfo() { 328 return displayedInfo; 329 } 330 331 /** 332 * Gets the errorKeys attribute. 333 * @return Returns the errorKeys. 334 */ 335 public List getErrorKeys() { 336 return errorKeys; 337 } 338 339 /** 340 * Sets the errorKeys attribute value. 341 * @param errorKeys The errorKeys to set. 342 */ 343 public void setErrorKeys(List errorKeys) { 344 this.errorKeys = errorKeys; 345 } 346 347 /** 348 * Determines if the current document is enrouted 349 */ 350 private boolean isDocumentEnrouted() { 351 KualiWorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument(); 352 353 return !workflowDocument.stateIsInitiated() && !workflowDocument.stateIsSaved(); 354 } 355 356 /** 357 * Determines if there is more than one editable line in this group; if so, then it allows deleting 358 */ 359 public void updateDeletabilityOfAllLines() { 360 if (this.isDocumentEnrouted()) { 361 if (hasEnoughAccountingLinesForDelete()) { 362 for (AccountingLineRenderingContext accountingLineRenderingContext : containers) { 363 if (accountingLineRenderingContext.isEditableLine()) { 364 accountingLineRenderingContext.makeDeletable(); 365 } 366 } 367 } 368 } else { 369 // we're pre-route - everybody is deletable! 370 for (AccountingLineRenderingContext accountingLineRenderingContext : containers) { 371 accountingLineRenderingContext.makeDeletable(); 372 } 373 } 374 } 375 376 /** 377 * Determines if there are enough accounting lines in this group for delete buttons to be present 378 * @return true if there are enough accounting lines for a delete, false otherwise 379 */ 380 protected boolean hasEnoughAccountingLinesForDelete() { 381 // 1. get the count of how many accounting lines are editable 382 int editableLineCount = 0; 383 for (AccountingLineRenderingContext accountingLineRenderingContext : containers) { 384 if (!accountingLineRenderingContext.isNewLine() && accountingLineRenderingContext.isEditableLine()) { 385 editableLineCount += 1; 386 } 387 if (editableLineCount == 2) return true; // we know we're good...skip out early 388 } 389 return false; 390 } 391 392 /** 393 * Gets the collectionItemPropertyName attribute. 394 * @return Returns the collectionItemPropertyName. 395 */ 396 public String getCollectionItemPropertyName() { 397 return collectionItemPropertyName; 398 } 399 400 /** 401 * Gets the groupDefinition attribute. 402 * @return Returns the groupDefinition. 403 */ 404 public AccountingLineGroupDefinition getGroupDefinition() { 405 return groupDefinition; 406 } 407 408 /** 409 * Sets the groupDefinition attribute value. 410 * @param groupDefinition The groupDefinition to set. 411 */ 412 public void setGroupDefinition(AccountingLineGroupDefinition groupDefinition) { 413 this.groupDefinition = groupDefinition; 414 } 415 416 /** 417 * Gets the displayedErrors attribute. 418 * @return Returns the displayedErrors. 419 */ 420 public Map getDisplayedErrors() { 421 return displayedErrors; 422 } 423 424 /** 425 * Sets the displayedErrors attribute value. 426 * @param displayedErrors The displayedErrors to set. 427 */ 428 public void setDisplayedErrors(Map displayedErrors) { 429 this.displayedErrors = displayedErrors; 430 } 431 432 /** 433 * Gets the collectionPropertyName attribute. 434 * @return Returns the collectionPropertyName. 435 */ 436 public String getCollectionPropertyName() { 437 return collectionPropertyName; 438 } 439 440 /** 441 * Sets the collectionPropertyName attribute value. 442 * @param collectionPropertyName The collectionPropertyName to set. 443 */ 444 public void setCollectionPropertyName(String collectionPropertyName) { 445 this.collectionPropertyName = collectionPropertyName; 446 } 447 448 /** 449 * Sets the collectionItemPropertyName attribute value. 450 * @param collectionItemPropertyName The collectionItemPropertyName to set. 451 */ 452 public void setCollectionItemPropertyName(String collectionItemPropertyName) { 453 this.collectionItemPropertyName = collectionItemPropertyName; 454 } 455 456 457 public AccountingDocument getAccountingDocument() { 458 return accountingDocument; 459 } 460 461 public void setAccountingDocument(AccountingDocument accountingDocument) { 462 this.accountingDocument = accountingDocument; 463 } 464 }