View Javadoc
1   /*
2    * Copyright 2008 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.document.web;
17  
18  import java.io.IOException;
19  import java.util.List;
20  import java.util.Map;
21  
22  import javax.servlet.jsp.JspException;
23  import javax.servlet.jsp.PageContext;
24  import javax.servlet.jsp.tagext.JspFragment;
25  import javax.servlet.jsp.tagext.Tag;
26  
27  import org.kuali.ole.sys.document.AccountingDocument;
28  import org.kuali.ole.sys.document.datadictionary.AccountingLineGroupDefinition;
29  import org.kuali.ole.sys.document.datadictionary.TotalDefinition;
30  import org.kuali.ole.sys.document.web.renderers.CellCountCurious;
31  import org.kuali.ole.sys.document.web.renderers.CollectionPropertiesCurious;
32  import org.kuali.ole.sys.document.web.renderers.GroupErrorsRenderer;
33  import org.kuali.ole.sys.document.web.renderers.GroupTitleLineRenderer;
34  import org.kuali.ole.sys.document.web.renderers.Renderer;
35  import org.kuali.ole.sys.document.web.renderers.RepresentedCellCurious;
36  import org.kuali.rice.kew.api.WorkflowDocument;
37  import org.kuali.rice.krad.util.GlobalVariables;
38  
39  /**
40   * This represents an accounting line group in renderable state
41   */
42  public class DefaultAccountingLineGroupImpl implements AccountingLineGroup {
43      protected AccountingLineGroupDefinition groupDefinition;
44      protected JspFragment importLineOverride;
45      protected String collectionPropertyName;
46      protected List<? extends AccountingLineRenderingContext> containers;
47      protected AccountingDocument accountingDocument;
48      protected int cellCount = 0;
49      protected int arbitrarilyHighIndex;
50      protected Map<String, Object> displayedErrors;
51      protected Map<String, Object> displayedWarnings;
52      protected Map<String, Object> displayedInfo;
53      protected boolean canEdit;
54      protected String collectionItemPropertyName;
55      protected List errorKeys;
56      
57      /**
58       * Constructs a DefaultAccountingLineGroupImpl
59       */
60      public DefaultAccountingLineGroupImpl() {}
61  
62      /**
63       * Initializes the DefaultAccountingLineGroupImpl
64       * 
65       * @param groupDefinition the data dictionary group definition for this accounting line group
66       * @param accountingDocument the document which owns or will own the accounting line being rendered
67       * @param containers the containers within this group
68       * @param collectionPropertyName the property name of the collection of accounting lines owned by this group
69       * @param errors a List of errors keys for errors on the page
70       * @param displayedErrors a Map of errors that have already been displayed
71       * @param canEdit determines if the page can be edited or not
72       */
73      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) {
74          this.groupDefinition = groupDefinition;
75          this.accountingDocument = accountingDocument;
76          this.containers = containers;
77          this.collectionPropertyName = collectionPropertyName;
78          this.collectionItemPropertyName = collectionItemPropertyName;
79          this.displayedErrors = displayedErrors;
80          this.displayedWarnings = displayedWarnings;
81          this.displayedInfo = displayedInfo;
82          this.canEdit = canEdit;
83      }
84  
85      /**
86       * Renders the whole of this accounting line group
87       * 
88       * @param pageContext the page context to render to
89       * @param parentTag the AccountingLinesTag that is requesting this rendering
90       */
91      public void renderEverything(PageContext pageContext, Tag parentTag) throws JspException {
92          if (groupDefinition.isHeaderRendering()) {
93              renderGroupHeader(pageContext, parentTag);
94          }
95          renderAccountingLineContainers(pageContext, parentTag);
96  
97          boolean renderTotals = !accountingDocument.getSourceAccountingLines().isEmpty() || !accountingDocument.getTargetAccountingLines().isEmpty();
98          renderTotals &= groupDefinition.getTotals() != null && groupDefinition.getTotals().size() > 0;
99          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 }