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}