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    }