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 }