001/**
002 * Copyright 2005-2015 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.rice.kew.rule.bo;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.uif.RemotableAttributeError;
020import org.kuali.rice.core.api.util.ConcreteKeyValue;
021import org.kuali.rice.core.api.util.KeyValue;
022import org.kuali.rice.core.api.util.RiceKeyConstants;
023import org.kuali.rice.core.web.format.BooleanFormatter;
024import org.kuali.rice.core.web.format.CollectionFormatter;
025import org.kuali.rice.core.web.format.DateFormatter;
026import org.kuali.rice.core.web.format.Formatter;
027import org.kuali.rice.kew.api.KewApiServiceLocator;
028import org.kuali.rice.kew.api.rule.RuleTemplate;
029import org.kuali.rice.kew.api.rule.RuleTemplateAttribute;
030import org.kuali.rice.kew.lookupable.MyColumns;
031import org.kuali.rice.kew.rule.RuleBaseValues;
032import org.kuali.rice.kew.rule.WorkflowRuleAttributeRows;
033import org.kuali.rice.kew.rule.service.RuleServiceInternal;
034import org.kuali.rice.kew.service.KEWServiceLocator;
035import org.kuali.rice.kim.api.KimConstants;
036import org.kuali.rice.kim.api.group.Group;
037import org.kuali.rice.kim.api.identity.Person;
038import org.kuali.rice.kim.api.identity.principal.Principal;
039import org.kuali.rice.kim.api.services.KimApiServiceLocator;
040import org.kuali.rice.kns.document.authorization.BusinessObjectRestrictions;
041import org.kuali.rice.kns.lookup.HtmlData;
042import org.kuali.rice.kns.lookup.LookupableHelperService;
043import org.kuali.rice.kns.service.KNSServiceLocator;
044import org.kuali.rice.kns.web.comparator.CellComparatorHelper;
045import org.kuali.rice.kns.web.struts.form.LookupForm;
046import org.kuali.rice.kns.web.ui.Column;
047import org.kuali.rice.kns.web.ui.Field;
048import org.kuali.rice.kns.web.ui.ResultRow;
049import org.kuali.rice.kns.web.ui.Row;
050import org.kuali.rice.krad.bo.BusinessObject;
051import org.kuali.rice.krad.bo.PersistableBusinessObject;
052import org.kuali.rice.krad.data.KradDataServiceLocator;
053import org.kuali.rice.krad.exception.ValidationException;
054import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
055import org.kuali.rice.krad.util.GlobalVariables;
056import org.kuali.rice.krad.util.KRADConstants;
057import org.kuali.rice.krad.util.KRADUtils;
058
059import java.sql.Date;
060import java.util.ArrayList;
061import java.util.Collection;
062import java.util.HashMap;
063import java.util.Iterator;
064import java.util.List;
065import java.util.Map;
066
067/**
068 * This is a description of what this class does - jjhanso don't forget to fill this in.
069 *
070 * @author Kuali Rice Team (rice.collab@kuali.org)
071 *
072 */
073public class RuleBaseValuesLookupableHelperServiceImpl extends AbstractRuleLookupableHelperServiceImpl {
074
075    private LookupableHelperService ruleDelegationLookupableHelperService;
076    private List<?> delegationPkNames;
077
078    private static final String RULE_TEMPLATE_PROPERTY_NAME = "ruleTemplate.name";
079    private static final String RULE_ID_PROPERTY_NAME = "id";
080    private static final String RULE_TEMPLATE_ID_PROPERTY_NAME = "ruleTemplateId";
081    private static final String ACTIVE_IND_PROPERTY_NAME = "active";
082    private static final String DELEGATE_RULE_PROPERTY_NAME = "delegateRule";
083    private static final String DOC_TYP_NAME_PROPERTY_NAME = "documentType.name";
084    private static final String RULE_DESC_PROPERTY_NAME = "description";
085
086    @Override
087    public boolean checkForAdditionalFields(Map<String, String> fieldValues) {
088        String ruleTemplateNameParam = fieldValues.get(RULE_TEMPLATE_PROPERTY_NAME);
089
090        return super.checkForAdditionalFields(fieldValues, ruleTemplateNameParam);
091    }
092
093    @Override
094    public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) {
095        String docTypeNameParam = fieldValues.get(DOC_TYP_NAME_PROPERTY_NAME);
096        String ruleTemplateIdParam = fieldValues.get(RULE_TEMPLATE_ID_PROPERTY_NAME);
097        String ruleTemplateNameParam = fieldValues.get(RULE_TEMPLATE_PROPERTY_NAME);
098        String groupIdParam = fieldValues.get(GROUP_REVIEWER_PROPERTY_NAME);
099        String groupNameParam = fieldValues.get(GROUP_REVIEWER_NAME_PROPERTY_NAME);
100        String groupNamespaceParam = fieldValues.get(GROUP_REVIEWER_NAMESPACE_PROPERTY_NAME);
101        String networkIdParam = fieldValues.get(PERSON_REVIEWER_PROPERTY_NAME);
102        String userDirectiveParam = fieldValues.get(PERSON_REVIEWER_TYPE_PROPERTY_NAME);
103        String activeParam = fieldValues.get(ACTIVE_IND_PROPERTY_NAME);
104        String ruleIdParam = fieldValues.get(RULE_ID_PROPERTY_NAME);
105        String ruleDescription = fieldValues.get(RULE_DESC_PROPERTY_NAME);
106        String deleteSelection = fieldValues.get(DELEGATE_RULE_PROPERTY_NAME);
107
108        String docTypeSearchName = null;
109        String workflowId = null;
110        String workgroupId = null;
111        String ruleTemplateId = null;
112        Boolean isDelegateRule = null;
113        Boolean isActive = null;
114        String ruleId = null;
115
116
117        //for KULRICE-3678
118        if(StringUtils.isNotBlank(deleteSelection))
119        {
120                if(deleteSelection.equalsIgnoreCase("Y")) {
121                                isDelegateRule = Boolean.TRUE;
122                        } else {
123                                isDelegateRule = Boolean.FALSE;
124                        }
125        }
126
127        if (StringUtils.isNotBlank(ruleIdParam)) {
128            ruleId = ruleIdParam.trim();
129        }
130
131        if (StringUtils.isNotBlank(activeParam)) {
132            if (activeParam.equals("Y")) {
133                isActive = Boolean.TRUE;
134            } else {
135                isActive = Boolean.FALSE;
136            }
137        }
138
139        if (StringUtils.isNotBlank(docTypeNameParam)) {
140            docTypeSearchName = docTypeNameParam.replace('*', '%');
141            docTypeSearchName = "%" + docTypeSearchName.trim() + "%";
142        }
143
144        if (StringUtils.isNotBlank(networkIdParam)) {
145            Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(networkIdParam);
146                if (principal != null) {
147                        workflowId = principal.getPrincipalId();
148                }
149        }
150
151        if (!StringUtils.isEmpty(groupIdParam) || !StringUtils.isEmpty(groupNameParam)) {
152            Group group = null;
153            if (groupIdParam != null && !"".equals(groupIdParam)) {
154                group = getGroupService().getGroup(groupIdParam.trim());
155            } else {
156                if (groupNamespaceParam == null) {
157                    groupNamespaceParam = KimConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE;
158                }
159                group = getGroupService().getGroupByNamespaceCodeAndName(groupNamespaceParam, groupNameParam.trim());
160                if (group == null) {
161                    GlobalVariables.getMessageMap().putError(GROUP_REVIEWER_NAMESPACE_PROPERTY_NAME, RiceKeyConstants.ERROR_CUSTOM, INVALID_WORKGROUP_ERROR);
162                } else {
163                    workgroupId = group.getId();
164                }
165            }
166        }
167
168        Map<String, String> attributes = null;
169        MyColumns myColumns = new MyColumns();
170        if (StringUtils.isNotBlank(ruleTemplateNameParam) || StringUtils.isNotBlank(ruleTemplateIdParam) && !"null".equals(ruleTemplateIdParam)) {
171            RuleTemplate ruleTemplate = null;
172            if (StringUtils.isNotBlank(ruleTemplateIdParam)) {
173                ruleTemplate = KewApiServiceLocator.getRuleService().getRuleTemplate(ruleTemplateIdParam);
174            } else {
175                ruleTemplate = KewApiServiceLocator.getRuleService().getRuleTemplateByName(ruleTemplateNameParam.trim());
176                ruleTemplateId = ruleTemplate.getId();
177            }
178
179            attributes = new HashMap<String, String>();
180            for (RuleTemplateAttribute ruleTemplateAttribute : ruleTemplate.getActiveRuleTemplateAttributes()) {
181                if (!RuleAttribute.isWorkflowAttribute(ruleTemplateAttribute.getRuleAttribute().getType())) {
182                    continue;
183                }
184                WorkflowRuleAttributeRows workflowRuleAttributeRows =
185                        KEWServiceLocator.getWorkflowRuleAttributeMediator().getSearchRows(fieldValues, ruleTemplateAttribute, false);
186
187                for (RemotableAttributeError error : workflowRuleAttributeRows.getValidationErrors()) {
188                    GlobalVariables.getMessageMap().putError(error.getAttributeName(), RiceKeyConstants.ERROR_CUSTOM, error.getMessage());
189                }
190
191                for (Row row : workflowRuleAttributeRows.getRows()) {
192                    for (Field field : row.getFields()) {
193                        if (fieldValues.get(field.getPropertyName()) != null) {
194                            String attributeParam = fieldValues.get(field.getPropertyName());
195                            if (!attributeParam.equals("")) {
196                                attributes.put(field.getPropertyName(), attributeParam.trim());
197                            }
198                        }
199                        if (field.getFieldType().equals(Field.TEXT) || field.getFieldType().equals(Field.DROPDOWN) || field.getFieldType().equals(Field.DROPDOWN_REFRESH) || field.getFieldType().equals(Field.RADIO)) {
200                            myColumns.getColumns().add(new ConcreteKeyValue(field.getPropertyName(), ruleTemplateAttribute.getId()));
201                        }
202                    }
203                }
204            }
205        }
206
207        if (!StringUtils.isEmpty(ruleDescription)) {
208            ruleDescription = ruleDescription.replace('*', '%');
209            ruleDescription = "%" + ruleDescription.trim() + "%";
210        }
211
212        if (!GlobalVariables.getMessageMap().hasNoErrors()) {
213            throw new ValidationException("errors in search criteria");
214        }
215
216        // TODO: replace this with new API find method ??
217        List<RuleBaseValues> rules = getRuleService().search(docTypeSearchName, ruleId, ruleTemplateId, ruleDescription, workgroupId, workflowId, isDelegateRule, isActive, attributes, userDirectiveParam);
218        List<RuleBaseValues> displayList = new ArrayList<RuleBaseValues>();
219
220        for (RuleBaseValues record : rules) {
221            if (StringUtils.isEmpty(record.getDescription())) {
222                record.setDescription("");
223            }
224
225            if (ruleTemplateNameParam != null && !ruleTemplateNameParam.trim().equals("") || ruleTemplateIdParam != null && !"".equals(ruleTemplateIdParam) && !"null".equals(ruleTemplateIdParam)) {
226                MyColumns myNewColumns = new MyColumns();
227                for (KeyValue pair : myColumns.getColumns()) {
228                    final KeyValue newPair;
229                    if (record.getRuleExtensionValue(pair.getValue(), pair.getKey().toString()) != null) {
230                        newPair = new ConcreteKeyValue(pair.getKey(), record.getRuleExtensionValue(pair.getValue(), pair.getKey().toString()).getValue());
231                    } else {
232                        newPair = new ConcreteKeyValue(pair.getKey(), "");
233                    }
234                    myNewColumns.getColumns().add(newPair);
235                    record.getFieldValues().put(newPair.getKey(), newPair.getValue());
236                }
237                record.setMyColumns(myNewColumns);
238            }
239
240            StringBuffer returnUrl = new StringBuffer("<a href=\"");
241            returnUrl.append(fieldValues.get(BACK_LOCATION)).append("?methodToCall=refresh&docFormKey=").append(fieldValues.get(DOC_FORM_KEY)).append("&");
242
243            returnUrl.append(RULE_ID_PROPERTY_NAME);
244            returnUrl.append("=").append(record.getId()).append("\">return value</a>");
245            record.setReturnUrl(returnUrl.toString());
246
247            String destinationUrl = "<a href=\"Rule.do?methodToCall=report&currentRuleId=" + record.getId() + "\">report</a>";
248
249            record.setDestinationUrl(destinationUrl);
250
251            displayList.add(record);
252        }
253        return displayList;
254
255    }
256
257    private RuleServiceInternal getRuleService() {
258        return (RuleServiceInternal) KEWServiceLocator.getService(KEWServiceLocator.RULE_SERVICE);
259    }
260
261    @Override
262    public Collection performLookup(LookupForm lookupForm,
263            Collection resultTable, boolean bounded) {
264        //return super.performLookup(lookupForm, resultTable, bounded);
265        setBackLocation((String) lookupForm.getFieldsForLookup().get(KRADConstants.BACK_LOCATION));
266        setDocFormKey((String) lookupForm.getFieldsForLookup().get(KRADConstants.DOC_FORM_KEY));
267        Collection displayList;
268
269        // call search method to get results
270        if (bounded) {
271            displayList = getSearchResults(lookupForm.getFieldsForLookup());
272        }
273        else {
274            displayList = getSearchResultsUnbounded(lookupForm.getFieldsForLookup());
275        }
276
277        HashMap<String,Class> propertyTypes = new HashMap<String, Class>();
278
279        boolean hasReturnableRow = false;
280
281        List returnKeys = getReturnKeys();
282
283        List pkNames = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(getBusinessObjectClass());
284        Person user = GlobalVariables.getUserSession().getPerson();
285
286        // iterate through result list and wrap rows with return url and action urls
287        for (Iterator iter = displayList.iterator(); iter.hasNext();) {
288            BusinessObject element = (BusinessObject) iter.next();
289            if(element instanceof PersistableBusinessObject){
290                lookupForm.setLookupObjectId(((PersistableBusinessObject)element).getObjectId());
291            }
292
293            BusinessObjectRestrictions businessObjectRestrictions = getBusinessObjectAuthorizationService().getLookupResultRestrictions(element, user);
294
295            HtmlData returnUrl = getReturnUrl(element, lookupForm, returnKeys, businessObjectRestrictions);
296
297            String actionUrls = getActionUrls(element, pkNames, businessObjectRestrictions);
298            //Fix for JIRA - KFSMI-2417
299            if("".equals(actionUrls)){
300                actionUrls = ACTION_URLS_EMPTY;
301            }
302
303            // Determine whether or not this rule is a delegate rule.
304            boolean isRuleDelegation = (element instanceof RuleBaseValues && ((RuleBaseValues) element).getDelegateRule().booleanValue());
305
306            List<Column> columns = getColumns();
307            for (Object element2 : columns) {
308
309                Column col = (Column) element2;
310                Formatter formatter = col.getFormatter();
311
312                // pick off result column from result list, do formatting
313                String propValue = KRADConstants.EMPTY_STRING;
314                Object prop = null;
315                boolean skipPropTypeCheck = false;
316                //try to get value elsewhere
317                if (element instanceof RuleBaseValues) {
318                    prop = ((RuleBaseValues)element).getFieldValues().get(col.getPropertyName());
319                    skipPropTypeCheck = true;
320                }
321                if (prop == null) {
322                    prop = KradDataServiceLocator.getDataObjectService().wrap(element).getPropertyValueNullSafe(col.getPropertyName());
323                }
324
325                // set comparator and formatter based on property type
326                Class propClass = propertyTypes.get(col.getPropertyName());
327                if ( propClass == null && !skipPropTypeCheck) {
328                    try {
329                        propClass = KRADServiceLocatorWeb.getLegacyDataAdapter().getPropertyType(element, col.getPropertyName());
330                        propertyTypes.put( col.getPropertyName(), propClass );
331                    } catch (Exception e) {
332                        throw new RuntimeException("Cannot access PropertyType for property " + "'" + col.getPropertyName() + "' " + " on an instance of '" + element.getClass().getName() + "'.", e);
333                    }
334                }
335
336                // formatters
337                if (prop != null) {
338                    // for Booleans, always use BooleanFormatter
339                    if (prop instanceof Boolean) {
340                        formatter = new BooleanFormatter();
341                    }
342
343                    // for Dates, always use DateFormatter
344                    if (prop instanceof Date) {
345                        formatter = new DateFormatter();
346                    }
347
348                    // for collection, use the list formatter if a formatter hasn't been defined yet
349                    if (prop instanceof Collection && formatter == null) {
350                    formatter = new CollectionFormatter();
351                    }
352
353                    if (formatter != null) {
354                        propValue = (String) formatter.format(prop);
355                    }
356                    else {
357                        propValue = prop.toString();
358                    }
359                }
360
361                // comparator
362                col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass));
363                col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass));
364
365                propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue, businessObjectRestrictions);
366
367                col.setPropertyValue(propValue);
368
369                if (StringUtils.isNotBlank(propValue)) {
370                        if (RULE_ID_PROPERTY_NAME.equals(col.getPropertyName()) && isRuleDelegation) {
371                                // If the row represents a delegate rule, make the ID column's inquiry link lead to the corresponding delegate rule instead.
372                                List<?> delegationList = KEWServiceLocator.getRuleDelegationService().findByDelegateRuleId(
373                                                ((RuleBaseValues) element).getId());
374                                if (KRADUtils.isNotNull(delegationList) && !delegationList.isEmpty()) {
375                                        BusinessObject ruleDelegation = (BusinessObject) delegationList.get(0);
376                                        col.setColumnAnchor(getInquiryUrl(ruleDelegation, "ruleDelegationId"));
377                                } else {
378                                        col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName()));
379                                }
380                        }else {
381                                col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName()));
382                        }
383
384                }
385            }
386
387            ResultRow row = new ResultRow(columns, returnUrl.constructCompleteHtmlTag(), actionUrls);
388            row.setRowId(returnUrl.getName());
389            row.setReturnUrlHtmlData(returnUrl);
390            // because of concerns of the BO being cached in session on the ResultRow,
391            // let's only attach it when needed (currently in the case of export)
392            if (getBusinessObjectDictionaryService().isExportable(getBusinessObjectClass())) {
393                row.setBusinessObject(element);
394            }
395            if(element instanceof PersistableBusinessObject){
396                row.setObjectId((((PersistableBusinessObject)element).getObjectId()));
397            }
398
399
400            boolean rowReturnable = isResultReturnable(element);
401            row.setRowReturnable(rowReturnable);
402            if (rowReturnable) {
403                hasReturnableRow = true;
404            }
405            resultTable.add(row);
406        }
407
408        lookupForm.setHasReturnableRow(hasReturnableRow);
409
410        return displayList;
411    }
412
413    @Override
414    public List<HtmlData> getCustomActionUrls(BusinessObject businessObject,
415            List pkNames) {
416        RuleBaseValues ruleBaseValues = (RuleBaseValues)businessObject;
417        List<HtmlData> htmlDataList = new ArrayList<HtmlData>();
418        if (StringUtils.isNotBlank(ruleBaseValues.getRuleTemplateName()) && StringUtils.isNotBlank(getMaintenanceDocumentTypeName())) {
419                if (ruleBaseValues.getDelegateRule().booleanValue()) {
420                        // If the rule is a delegate rule, have the edit/copy links open the rule delegation maintenance document screen instead.
421                        List<?> delegationList = KEWServiceLocator.getRuleDelegationService().findByDelegateRuleId(ruleBaseValues.getId());
422                        if (KRADUtils.isNotNull(delegationList) && !delegationList.isEmpty()) {
423                                BusinessObject ruleDelegation = (BusinessObject) delegationList.get(0);
424                                // Retrieve the rule delegation lookupable helper service and the primary key names, if they have not been obtained yet.
425                        if (ruleDelegationLookupableHelperService == null) {
426                                        ruleDelegationLookupableHelperService = KNSServiceLocator.getLookupable(
427                                KNSServiceLocator.getBusinessObjectDictionaryService()
428                                        .getLookupableID(ruleDelegation.getClass())).getLookupableHelperService();
429                                        if (ruleDelegationLookupableHelperService.getBusinessObjectClass() == null) {
430                                                ruleDelegationLookupableHelperService.setBusinessObjectClass(ruleDelegation.getClass());
431                                        }
432                        delegationPkNames = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(ruleDelegation.getClass());
433                                }
434                        // Allow the rule delegation's lookupable helper service to handle the custom action URL generation instead.
435                                htmlDataList = ruleDelegationLookupableHelperService.getCustomActionUrls(ruleDelegation, delegationPkNames);
436                        }
437                } else {
438                        // Otherwise, have the links open the regular routing rule maintenance document screen.
439                        if (allowsMaintenanceEditAction(businessObject)) {
440                                htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames));
441                        }
442                        if (allowsMaintenanceNewOrCopyAction()) {
443                        htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_COPY_METHOD_TO_CALL, pkNames));
444                }
445                }
446        }
447
448        return htmlDataList;
449    }
450
451}