001    /**
002     * Copyright 2005-2012 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.rice.kew.rule.web;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.apache.struts.action.ActionForm;
020    import org.apache.struts.action.ActionForward;
021    import org.apache.struts.action.ActionMapping;
022    import org.apache.struts.action.ActionMessages;
023    import org.kuali.rice.kew.actionrequest.ActionRequestValue;
024    import org.kuali.rice.kew.api.KewApiConstants;
025    import org.kuali.rice.kew.api.action.ActionRequestStatus;
026    import org.kuali.rice.kew.doctype.bo.DocumentType;
027    import org.kuali.rice.kew.doctype.service.DocumentTypeService;
028    import org.kuali.rice.kew.engine.ActivationContext;
029    import org.kuali.rice.kew.engine.RouteContext;
030    import org.kuali.rice.kew.engine.node.RouteNode;
031    import org.kuali.rice.kew.engine.node.RouteNodeInstance;
032    import org.kuali.rice.kew.exception.WorkflowServiceError;
033    import org.kuali.rice.kew.routeheader.AttributeDocumentContent;
034    import org.kuali.rice.kew.routeheader.DocumentContent;
035    import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
036    import org.kuali.rice.kew.routelog.web.RouteLogAction;
037    import org.kuali.rice.kew.routelog.web.RouteLogForm;
038    import org.kuali.rice.kew.rule.FlexRM;
039    import org.kuali.rice.kew.rule.WorkflowRuleAttribute;
040    import org.kuali.rice.kew.rule.bo.RuleAttribute;
041    import org.kuali.rice.kew.rule.bo.RuleTemplateAttributeBo;
042    import org.kuali.rice.kew.rule.bo.RuleTemplateBo;
043    import org.kuali.rice.kew.rule.service.RuleTemplateService;
044    import org.kuali.rice.kew.rule.xmlrouting.GenericXMLRuleAttribute;
045    import org.kuali.rice.kew.service.KEWServiceLocator;
046    import org.kuali.rice.kew.api.KewApiConstants;
047    import org.kuali.rice.kew.web.KewKualiAction;
048    import org.kuali.rice.kns.web.ui.Field;
049    import org.kuali.rice.kns.web.ui.Row;
050    import org.kuali.rice.krad.UserSession;
051    import org.kuali.rice.krad.exception.ValidationException;
052    import org.kuali.rice.krad.util.GlobalVariables;
053    
054    import javax.servlet.http.HttpServletRequest;
055    import javax.servlet.http.HttpServletResponse;
056    import java.sql.Timestamp;
057    import java.text.SimpleDateFormat;
058    import java.util.ArrayList;
059    import java.util.Calendar;
060    import java.util.Collections;
061    import java.util.Date;
062    import java.util.HashMap;
063    import java.util.HashSet;
064    import java.util.Iterator;
065    import java.util.List;
066    import java.util.Map;
067    import java.util.Set;
068    
069    
070    /**
071     * A Struts Action for executing routing reports and retrieving the results.
072     *
073     * @author Kuali Rice Team (rice.collab@kuali.org)
074     */
075    public class RoutingReportAction extends KewKualiAction {
076            private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RoutingReportAction.class);
077    
078            public static final String DOC_TYPE_REPORTING = "documentType";
079            public static final String TEMPLATE_REPORTING = "template";
080    
081        @Override
082        public ActionForward execute(ActionMapping mapping, ActionForm form,
083                HttpServletRequest request, HttpServletResponse response)
084                throws Exception {
085            this.initiateForm(request, form);
086            RoutingReportForm routingForm = (RoutingReportForm)form;
087            if (org.apache.commons.lang.StringUtils.isEmpty(routingForm.getDateRef())) {
088                SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
089                routingForm.setEffectiveHour("5");
090                routingForm.setEffectiveMinute("0");
091                routingForm.setAmPm("1");
092                routingForm.setDateRef(sdf.format(new Date()));
093                routingForm.setReportType(TEMPLATE_REPORTING);
094            }
095            if (DOC_TYPE_REPORTING.equals(routingForm.getReportType())) {
096                if (org.apache.commons.lang.StringUtils.isEmpty(routingForm.getDocumentTypeParam())) {
097                    throw new RuntimeException("No document type was given");
098                }
099                if (org.apache.commons.lang.StringUtils.isEmpty(routingForm.getInitiatorPrincipalId())) {
100                    throw new RuntimeException("No initiator principal id was given");
101                }
102                if (org.apache.commons.lang.StringUtils.isEmpty(routingForm.getDocumentContent())) {
103                    throw new RuntimeException("No document content was given");
104                }
105            } else if (!(TEMPLATE_REPORTING.equals(routingForm.getReportType()))) {
106                // report type is not Document Type or Template Type... error out
107                throw new RuntimeException("The Routing Report type is not set");
108            }
109            return super.execute(mapping, form, request, response);
110        }
111    
112            public ActionForward calculateRoute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
113                    RoutingReportForm routingForm = (RoutingReportForm) form;
114    
115            // this is never actually used??
116                    List<WorkflowServiceError> errors = new ArrayList<WorkflowServiceError>();
117    
118                    if (getDocumentTypeService().findByName(routingForm.getDocumentType()) == null) {
119                        GlobalVariables.getMessageMap().putError("Document type is required.", "doctype.documenttypeservice.doctypename.required");
120                    }
121                    Timestamp date = null;
122                    if (!org.apache.commons.lang.StringUtils.isEmpty(routingForm.getDateRef())) {
123                            SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
124                            try {
125                                    Calendar calendar = Calendar.getInstance();
126                                    calendar.setTime(sdf.parse(routingForm.getDateRef()));
127                                    calendar.set(Calendar.HOUR, Integer.parseInt(routingForm.getEffectiveHour()));
128                                    calendar.set(Calendar.MINUTE, Integer.parseInt(routingForm.getEffectiveMinute()));
129                                    calendar.set(Calendar.AM_PM, Integer.parseInt(routingForm.getAmPm()));
130                                    date = new Timestamp(calendar.getTimeInMillis());
131                            } catch (Exception e) {
132                                    LOG.error("error parsing date", e);
133                                    GlobalVariables.getMessageMap().putError("Invalid date.", "routereport.effectiveDate.invalid");
134                            }
135                    }
136    
137                    if (!GlobalVariables.getMessageMap().hasNoErrors()) {
138                throw new ValidationException("Errors populating rule attributes.");
139            }
140    
141                    DocumentTypeService documentTypeService = (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
142                    DocumentType docType = documentTypeService.findByName(routingForm.getDocumentType());
143    
144                    DocumentRouteHeaderValue routeHeader = new DocumentRouteHeaderValue();
145                    routeHeader.setDocumentId("");
146                    routeHeader.setDocumentTypeId(docType.getDocumentTypeId());
147                    routeHeader.setDocRouteLevel(new Integer(0));
148            routeHeader.setDocVersion(new Integer(KewApiConstants.DocumentContentVersions.CURRENT));
149    
150            List<RouteReportRuleTemplateContainer> ruleTemplateContainers = new ArrayList<RouteReportRuleTemplateContainer>();
151                    if (routingForm.getReportType().equals(DOC_TYPE_REPORTING)) {
152    
153              List routeNodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(docType, true);
154                            for (Iterator iter = routeNodes.iterator(); iter.hasNext();) {
155                    RouteNode routeNode = (RouteNode) iter.next();
156                                    if (routeNode.isFlexRM()) {
157                                            RuleTemplateBo ruleTemplate = getRuleTemplateService().findByRuleTemplateName(routeNode.getRouteMethodName());
158                                            if (ruleTemplate != null) {
159                                                ruleTemplateContainers.add(new RouteReportRuleTemplateContainer(ruleTemplate, routeNode));
160                                                    if (ruleTemplate.getDelegationTemplate() != null) {
161                                                        ruleTemplateContainers.add(new RouteReportRuleTemplateContainer(ruleTemplate.getDelegationTemplate(), routeNode));
162                                                    }
163                                            }
164                                    }
165                            }
166                    } else {
167                            RuleTemplateBo ruleTemplate = getRuleTemplateService().findByRuleTemplateId(routingForm.getRuleTemplateId());
168                            RouteNode routeNode = new RouteNode();
169                            routeNode.setRouteNodeName(ruleTemplate.getName());
170                            ruleTemplateContainers.add(new RouteReportRuleTemplateContainer(ruleTemplate, routeNode));
171                            if (ruleTemplate.getDelegationTemplate() != null) {
172                                ruleTemplateContainers.add(new RouteReportRuleTemplateContainer(ruleTemplate.getDelegationTemplate(), routeNode));
173                            }
174                    }
175    
176            String xmlDocumentContent = routingForm.getDocumentContent();
177            if (routingForm.getReportType().equals(TEMPLATE_REPORTING)) {
178                List<WorkflowRuleAttribute> attributes = new ArrayList<WorkflowRuleAttribute>();
179                for (RouteReportRuleTemplateContainer ruleTemplateContainer : ruleTemplateContainers) {
180                    RuleTemplateBo ruleTemplate = ruleTemplateContainer.ruleTemplate;
181                    for (RuleTemplateAttributeBo ruleTemplateAttribute : ruleTemplate.getActiveRuleTemplateAttributes()) {
182                        if (!ruleTemplateAttribute.isWorkflowAttribute()) {
183                            continue;
184                        }
185                        WorkflowRuleAttribute workflowAttribute = ruleTemplateAttribute.getWorkflowAttribute();
186    
187                        RuleAttribute ruleAttribute = ruleTemplateAttribute.getRuleAttribute();
188                        if (ruleAttribute.getType().equals(KewApiConstants.RULE_XML_ATTRIBUTE_TYPE)) {
189                            ((GenericXMLRuleAttribute) workflowAttribute).setExtensionDefinition(RuleAttribute.to(ruleAttribute));
190                        }
191                        List attValidationErrors = workflowAttribute.validateRoutingData(routingForm.getFields());
192                        if (attValidationErrors != null && !attValidationErrors.isEmpty()) {
193                            errors.addAll(attValidationErrors);
194                        }
195                        attributes.add(workflowAttribute);
196                    }
197                }
198    
199                if (!GlobalVariables.getMessageMap().hasNoErrors()) {
200                    throw new ValidationException("errors in search criteria");
201                }
202    
203                DocumentContent docContent = new AttributeDocumentContent(attributes);
204                xmlDocumentContent = docContent.getDocContent();
205            }
206    
207                    routeHeader.setDocContent(xmlDocumentContent);
208                    routeHeader.setInitiatorWorkflowId(getUserSession(request).getPrincipalId());
209                    routeHeader.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD);
210                    routeHeader.setDocTitle("Routing Report");
211                    routeHeader.setRoutingReport(true);
212                    long magicCounter = 0;
213    
214                    FlexRM flexRM = new FlexRM(date);
215    
216                    int numberOfRules = 0;
217                    int numberOfActionRequests = 0;
218                    Set<String> alreadyProcessedRuleTemplateNames = new HashSet<String>();
219                    for (Object element : ruleTemplateContainers) {
220                            // initialize the RouteContext
221                        RouteContext context = RouteContext.createNewRouteContext();
222                    context.setActivationContext(new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION));
223                            try {
224                                RouteReportRuleTemplateContainer ruleTemplateContainer = (RouteReportRuleTemplateContainer) element;
225                                    RuleTemplateBo ruleTemplate = ruleTemplateContainer.ruleTemplate;
226                                    RouteNode routeLevel = ruleTemplateContainer.routeNode;
227    
228                                    if (!alreadyProcessedRuleTemplateNames.contains(ruleTemplate.getName())) {
229                                        alreadyProcessedRuleTemplateNames.add(ruleTemplate.getName());
230                                    List<ActionRequestValue> actionRequests = flexRM.getActionRequests(routeHeader, routeLevel, null, ruleTemplate.getName());
231    
232                                    numberOfActionRequests += actionRequests.size();
233                                    numberOfRules += flexRM.getNumberOfMatchingRules();
234    
235                                    magicCounter = populateActionRequestsWithRouteLevelInformationAndIterateMagicCounter(routeLevel, actionRequests, magicCounter);
236                                    //routeHeader.getActionRequests().addAll(actionRequests);
237                                    routeHeader.getSimulatedActionRequests().addAll(actionRequests);
238                                    }
239                            } finally {
240                                    RouteContext.clearCurrentRouteContext();
241                            }
242                    }
243    
244                    if (numberOfActionRequests == 0) {
245                            if (numberOfRules == 0) {
246                                GlobalVariables.getMessageMap().putError("*", "routereport.noRules");
247                            } else {
248                                GlobalVariables.getMessageMap().putError("*", "routereport.noMatchingRules");
249                            }
250                            if (GlobalVariables.getMessageMap().hasErrors()) {
251                        throw new ValidationException("errors in search criteria");
252                    }
253                    }
254    
255    
256                    // PROBLEM HERE!!!!
257                    RouteLogForm routeLogForm = new RouteLogForm();
258                    routeLogForm.setShowFuture(true);
259            if (StringUtils.isNotBlank(routingForm.getBackUrl())) {
260                routeLogForm.setReturnUrlLocation(routingForm.getBackUrl());
261            }
262            LOG.debug("Value of getDisplayCloseButton " + routingForm.getShowCloseButton());
263            LOG.debug("Value of isDisplayCloseButton " + routingForm.isDisplayCloseButton());
264            routeLogForm.setShowCloseButton(routingForm.isDisplayCloseButton());
265                    request.setAttribute("routeHeader", routeHeader);
266                    new RouteLogAction().populateRouteLogFormActionRequests(routeLogForm, routeHeader);
267                    request.setAttribute("KualiForm", routeLogForm);
268                    //END PROBLEM AREA
269    
270                    //return mapping.findForward("basic");
271                    return mapping.findForward("routeLog");
272            }
273    
274            private class RouteReportRuleTemplateContainer {
275                public RuleTemplateBo ruleTemplate = null;
276                public RouteNode routeNode = null;
277                public RouteReportRuleTemplateContainer(RuleTemplateBo template, RouteNode node) {
278                    this.ruleTemplate = template;
279                    this.routeNode = node;
280                }
281            }
282    
283            public long populateActionRequestsWithRouteLevelInformationAndIterateMagicCounter(RouteNode routeLevel, List<ActionRequestValue> actionRequests, long magicCounter) {
284    
285                    for (ActionRequestValue actionRequest : actionRequests) {
286                            populateActionRequestsWithRouteLevelInformationAndIterateMagicCounter(routeLevel, actionRequest.getChildrenRequests(), magicCounter);
287                            actionRequest.setStatus(ActionRequestStatus.INITIALIZED.getCode());
288    //                      actionRequest.setRouteMethodName(routeLevel.getRouteMethodName());
289                            RouteNodeInstance routeNode = new RouteNodeInstance();
290                            routeNode.setRouteNode(routeLevel);
291                            actionRequest.setNodeInstance(routeNode);
292                            actionRequest.setRouteLevel(new Integer(0));
293                            magicCounter++;
294                            actionRequest.setActionRequestId(String.valueOf(magicCounter));
295                    }
296                    return magicCounter;
297            }
298    
299            @Override
300            public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
301                return mapping.findForward("basic");
302            }
303    
304            private ActionMessages initiateForm(HttpServletRequest request, ActionForm form) throws Exception {
305            RoutingReportForm routingReportForm = (RoutingReportForm) form;
306            if (routingReportForm.getReportType() == null) {
307                // no report type means we must check for potential setup
308                if ( (!org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getDocumentTypeParam())) ||
309                     (!org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getInitiatorPrincipalId())) ||
310                     (!org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getDocumentContent())) ) {
311                    // at least one parameter was passed... attempt to use Doc Type Report
312                    routingReportForm.setReportType(DOC_TYPE_REPORTING);
313                } else {
314                    // no parameters passed... default to Template Type Rreport
315                    routingReportForm.setReportType(TEMPLATE_REPORTING);
316                }
317            }
318    
319            if (routingReportForm.getReportType().equals(DOC_TYPE_REPORTING)) {
320                if (org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getDocumentTypeParam())) {
321                    throw new RuntimeException("Document Type was not given");
322                } else {
323                    DocumentType docType = getDocumentTypeService().findByName(routingReportForm.getDocumentTypeParam());
324                    if (docType == null) {
325                        throw new RuntimeException("Document Type is invalid");
326                    }
327                }
328                if (org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getInitiatorPrincipalId())) {
329                    throw new RuntimeException("Initiator Principal ID was not given");
330                } else {
331                    KEWServiceLocator.getIdentityHelperService().getPrincipal(routingReportForm.getInitiatorPrincipalId());
332                }
333                if (org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getDocumentContent())) {
334                    throw new RuntimeException("Document Content was not given");
335                }
336    
337                if (!org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getDocumentType())) {
338                    DocumentType docType = getDocumentTypeService().findByName(routingReportForm.getDocumentType());
339                    if (docType == null) {
340                        throw new RuntimeException("Document Type is missing or invalid");
341                    }
342                    routingReportForm.getRuleTemplateAttributes().clear();
343                    List<RouteNode> routeNodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(docType, true);
344                    for (RouteNode routeNode : routeNodes) {
345                        if (routeNode.isFlexRM()) {
346                            RuleTemplateBo ruleTemplate = getRuleTemplateService().findByRuleTemplateName(routeNode.getRouteMethodName());
347                            if (ruleTemplate != null) {
348                                loadRuleTemplateOnForm(ruleTemplate, routingReportForm, request, false);
349                                if (ruleTemplate.getDelegationTemplate() != null) {
350                                    loadRuleTemplateOnForm(ruleTemplate.getDelegationTemplate(), routingReportForm, request, true);
351                                }
352                            }
353                        }
354                    }
355                }
356    //          routingReportForm.setShowFields(true);
357            } else if (routingReportForm.getReportType().equals(TEMPLATE_REPORTING)) {
358                routingReportForm.setRuleTemplates(getRuleTemplateService().findAll());
359                if (routingReportForm.getRuleTemplateId() != null) {
360                    RuleTemplateBo ruleTemplate = getRuleTemplateService().findByRuleTemplateId(routingReportForm.getRuleTemplateId());
361                    routingReportForm.getRuleTemplateAttributes().clear();
362                    loadRuleTemplateOnForm(ruleTemplate, routingReportForm, request, false);
363                    if (ruleTemplate.getDelegationTemplate() != null) {
364                        loadRuleTemplateOnForm(ruleTemplate.getDelegationTemplate(), routingReportForm, request, true);
365                    }
366                }
367            }
368            return null;
369            }
370    
371            private void loadRuleTemplateOnForm(RuleTemplateBo ruleTemplate, RoutingReportForm routingReportForm, HttpServletRequest request, boolean isDelegate) {
372    
373                    Map<String, String> fieldValues = new HashMap<String, String>();
374    
375                    List<RuleTemplateAttributeBo> ruleTemplateAttributes = ruleTemplate.getActiveRuleTemplateAttributes();
376                    Collections.sort(ruleTemplateAttributes);
377    
378                    List<Row> rows = new ArrayList<Row>();
379                    for (RuleTemplateAttributeBo ruleTemplateAttribute : ruleTemplateAttributes) {
380                            if (!ruleTemplateAttribute.isWorkflowAttribute()) {
381                                    continue;
382                            }
383                            WorkflowRuleAttribute workflowAttribute = ruleTemplateAttribute.getWorkflowAttribute();
384    
385                            RuleAttribute ruleAttribute = ruleTemplateAttribute.getRuleAttribute();
386                            if (ruleAttribute.getType().equals(KewApiConstants.RULE_XML_ATTRIBUTE_TYPE)) {
387                                    ((GenericXMLRuleAttribute) workflowAttribute).setExtensionDefinition(RuleAttribute.to(ruleAttribute));
388                            }
389                            for (Row row : workflowAttribute.getRoutingDataRows()) {
390    
391                                    List<Field> fields = new ArrayList<Field>();
392                                    for (Object element2 : row.getFields()) {
393                                            Field field = (Field) element2;
394                                            if (request.getParameter(field.getPropertyName()) != null) {
395                                                    field.setPropertyValue(request.getParameter(field.getPropertyName()));
396                                            } else if (routingReportForm.getFields() != null && !routingReportForm.getFields().isEmpty()) {
397                                                    field.setPropertyValue((String) routingReportForm.getFields().get(field.getPropertyName()));
398                                            }
399                                            fields.add(field);
400                                            fieldValues.put(field.getPropertyName(), field.getPropertyValue());
401                                    }
402                            }
403    
404                            workflowAttribute.validateRuleData(fieldValues);// populate attribute
405                            List<Row> rdRows = workflowAttribute.getRoutingDataRows();
406                            for (Row row : rdRows)
407                            {
408                                    List<Field> fields = new ArrayList<Field>();
409                                    List<Field> rowFields = row.getFields();
410                                    for (Field field : rowFields )
411                                    {
412                                            if (request.getParameter(field.getPropertyName()) != null) {
413                                                    field.setPropertyValue(request.getParameter(field.getPropertyName()));
414                                            } else if (routingReportForm.getFields() != null && !routingReportForm.getFields().isEmpty()) {
415                                                    field.setPropertyValue((String) routingReportForm.getFields().get(field.getPropertyName()));
416                                            }
417                                            fields.add(field);
418                                            fieldValues.put(field.getPropertyName(), field.getPropertyValue());
419                                    }
420                                    row.setFields(fields);
421                                    rows.add(row);
422    
423                            }
424                    }
425    
426                    routingReportForm.getFields().putAll(fieldValues);
427                    routingReportForm.getRuleTemplateAttributes().addAll(rows);
428                    routingReportForm.setShowFields(true);
429                    routingReportForm.setShowViewResults(true);
430            }
431    
432            public ActionForward loadTemplate(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
433                    RoutingReportForm routingReportForm = (RoutingReportForm) form;
434                    if (org.apache.commons.lang.StringUtils.isEmpty(routingReportForm.getDateRef())) {
435                            SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
436                            routingReportForm.setEffectiveHour("5");
437                            routingReportForm.setEffectiveMinute("0");
438                            routingReportForm.setAmPm("1");
439                            routingReportForm.setDateRef(sdf.format(new Date()));
440                    }
441                    return mapping.findForward("basic");
442            }
443    
444            private RuleTemplateService getRuleTemplateService() {
445                    return (RuleTemplateService) KEWServiceLocator.getService(KEWServiceLocator.RULE_TEMPLATE_SERVICE);
446            }
447    
448            private DocumentTypeService getDocumentTypeService() {
449                    return (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
450            }
451    
452            private UserSession getUserSession(HttpServletRequest request) {
453                return GlobalVariables.getUserSession();
454            }
455    
456    
457    
458    
459    }