001/* 002 * Copyright 2008 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 */ 016package org.kuali.ole.sys.document.web; 017 018import java.io.IOException; 019import java.util.List; 020import java.util.Map; 021 022import javax.servlet.jsp.JspException; 023import javax.servlet.jsp.PageContext; 024import javax.servlet.jsp.tagext.JspFragment; 025import javax.servlet.jsp.tagext.Tag; 026 027import org.kuali.ole.sys.document.AccountingDocument; 028import org.kuali.ole.sys.document.datadictionary.AccountingLineGroupDefinition; 029import org.kuali.ole.sys.document.datadictionary.TotalDefinition; 030import org.kuali.ole.sys.document.web.renderers.CellCountCurious; 031import org.kuali.ole.sys.document.web.renderers.CollectionPropertiesCurious; 032import org.kuali.ole.sys.document.web.renderers.GroupErrorsRenderer; 033import org.kuali.ole.sys.document.web.renderers.GroupTitleLineRenderer; 034import org.kuali.ole.sys.document.web.renderers.Renderer; 035import org.kuali.ole.sys.document.web.renderers.RepresentedCellCurious; 036import org.kuali.rice.kew.api.WorkflowDocument; 037import org.kuali.rice.krad.util.GlobalVariables; 038 039/** 040 * This represents an accounting line group in renderable state 041 */ 042public class DefaultAccountingLineGroupImpl implements AccountingLineGroup { 043 protected AccountingLineGroupDefinition groupDefinition; 044 protected JspFragment importLineOverride; 045 protected String collectionPropertyName; 046 protected List<? extends AccountingLineRenderingContext> 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 (AccountingLineRenderingContext line : containers) { 118 if (line.getRenderableCellCount() > max) { 119 max = line.getRenderableCellCount(); 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 (AccountingLineRenderingContext 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 AccountingLineRenderingContext 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 (AccountingLineRenderingContext container : containers) { 264 List<AccountingLineTableRow> tableRows = container.getRows(); 265 266 for (AccountingLineTableRow row : tableRows) { 267 List<AccountingLineTableCell> tableCells = row.getCells(); 268 int cumulativeDisplayCellCount = 0; 269 270 for (AccountingLineTableCell cell : tableCells) { 271 cumulativeDisplayCellCount += cell.getColSpan(); 272 List<RenderableElement> fields = cell.getRenderableElement(); 273 274 for (RenderableElement field : fields) { 275 if (field instanceof ElementNamable == false) { 276 continue; 277 } 278 279 if (((ElementNamable) field).getName().equals(propertyName)) { 280 return cumulativeDisplayCellCount; 281 } 282 } 283 } 284 } 285 } 286 287 return -1; 288 } 289 290 /** 291 * Sets the cellCount attribute value. 292 * 293 * @param cellCount The cellCount to set. 294 */ 295 public void setCellCount(int cellCount) { 296 this.cellCount = cellCount; 297 } 298 299 /** 300 * Sets the importLineOverride attribute value. 301 * 302 * @param importLineOverride The importLineOverride to set. 303 */ 304 public void setImportLineOverride(JspFragment importLineOverride) { 305 this.importLineOverride = importLineOverride; 306 } 307 308 /** 309 * Sets the form's arbitrarily high tab index 310 * 311 * @param arbitrarilyHighIndex the index to set 312 */ 313 public void setArbitrarilyHighIndex(int arbitrarilyHighIndex) { 314 this.arbitrarilyHighIndex = arbitrarilyHighIndex; 315 } 316 317 /** 318 * Gets the displayedWarnings attribute. 319 * @return Returns the displayedWarnings. 320 */ 321 public Map getDisplayedWarnings() { 322 return displayedWarnings; 323 } 324 325 /** 326 * Gets the displayedInfo attribute. 327 * @return Returns the displayedInfo. 328 */ 329 public Map getDisplayedInfo() { 330 return displayedInfo; 331 } 332 333 /** 334 * Gets the errorKeys attribute. 335 * @return Returns the errorKeys. 336 */ 337 public List getErrorKeys() { 338 return errorKeys; 339 } 340 341 /** 342 * Sets the errorKeys attribute value. 343 * @param errorKeys The errorKeys to set. 344 */ 345 public void setErrorKeys(List errorKeys) { 346 this.errorKeys = errorKeys; 347 } 348 349 /** 350 * Determines if the current document is enrouted 351 */ 352 private boolean isDocumentEnrouted() { 353 WorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument(); 354 355 return !workflowDocument.isInitiated() && !workflowDocument.isSaved(); 356 } 357 358 /** 359 * Determines if there is more than one editable line in this group; if so, then it allows deleting 360 */ 361 public void updateDeletabilityOfAllLines() { 362 if (this.isDocumentEnrouted()) { 363 if (hasEnoughAccountingLinesForDelete()) { 364 for (AccountingLineRenderingContext accountingLineRenderingContext : containers) { 365 if (accountingLineRenderingContext.isEditableLine()) { 366 accountingLineRenderingContext.makeDeletable(); 367 } 368 } 369 } 370 } else { 371 // we're pre-route - everybody is deletable! 372 for (AccountingLineRenderingContext accountingLineRenderingContext : containers) { 373 accountingLineRenderingContext.makeDeletable(); 374 } 375 } 376 } 377 378 /** 379 * Determines if there are enough accounting lines in this group for delete buttons to be present 380 * @return true if there are enough accounting lines for a delete, false otherwise 381 */ 382 protected boolean hasEnoughAccountingLinesForDelete() { 383 // 1. get the count of how many accounting lines are editable 384 int editableLineCount = 0; 385 for (AccountingLineRenderingContext accountingLineRenderingContext : containers) { 386 if (!accountingLineRenderingContext.isNewLine() && accountingLineRenderingContext.isEditableLine()) { 387 editableLineCount += 1; 388 } 389 if (editableLineCount == 2) return true; // we know we're good...skip out early 390 } 391 return false; 392 } 393 394 /** 395 * Gets the collectionItemPropertyName attribute. 396 * @return Returns the collectionItemPropertyName. 397 */ 398 public String getCollectionItemPropertyName() { 399 return collectionItemPropertyName; 400 } 401 402 /** 403 * Gets the groupDefinition attribute. 404 * @return Returns the groupDefinition. 405 */ 406 public AccountingLineGroupDefinition getGroupDefinition() { 407 return groupDefinition; 408 } 409 410 /** 411 * Sets the groupDefinition attribute value. 412 * @param groupDefinition The groupDefinition to set. 413 */ 414 public void setGroupDefinition(AccountingLineGroupDefinition groupDefinition) { 415 this.groupDefinition = groupDefinition; 416 } 417 418 /** 419 * Gets the displayedErrors attribute. 420 * @return Returns the displayedErrors. 421 */ 422 public Map getDisplayedErrors() { 423 return displayedErrors; 424 } 425 426 /** 427 * Sets the displayedErrors attribute value. 428 * @param displayedErrors The displayedErrors to set. 429 */ 430 public void setDisplayedErrors(Map displayedErrors) { 431 this.displayedErrors = displayedErrors; 432 } 433 434 /** 435 * Gets the collectionPropertyName attribute. 436 * @return Returns the collectionPropertyName. 437 */ 438 public String getCollectionPropertyName() { 439 return collectionPropertyName; 440 } 441 442 /** 443 * Sets the collectionPropertyName attribute value. 444 * @param collectionPropertyName The collectionPropertyName to set. 445 */ 446 public void setCollectionPropertyName(String collectionPropertyName) { 447 this.collectionPropertyName = collectionPropertyName; 448 } 449 450 /** 451 * Sets the collectionItemPropertyName attribute value. 452 * @param collectionItemPropertyName The collectionItemPropertyName to set. 453 */ 454 public void setCollectionItemPropertyName(String collectionItemPropertyName) { 455 this.collectionItemPropertyName = collectionItemPropertyName; 456 } 457 458 459 public AccountingDocument getAccountingDocument() { 460 return accountingDocument; 461 } 462 463 public void setAccountingDocument(AccountingDocument accountingDocument) { 464 this.accountingDocument = accountingDocument; 465 } 466}