View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.docsearch;
17  
18  import com.google.common.collect.Iterables;
19  import org.kuali.rice.core.api.uif.RemotableAttributeField;
20  import org.kuali.rice.core.api.util.ConcreteKeyValue;
21  import org.kuali.rice.core.api.util.KeyValue;
22  import org.kuali.rice.kew.doctype.ApplicationDocumentStatus;
23  import org.kuali.rice.kew.doctype.bo.DocumentType;
24  import org.kuali.rice.kew.engine.node.RouteNode;
25  import org.kuali.rice.kew.framework.document.search.DocumentSearchCriteriaConfiguration;
26  import org.kuali.rice.kew.service.KEWServiceLocator;
27  import org.kuali.rice.kew.api.KewApiConstants;
28  import org.kuali.rice.kns.util.FieldUtils;
29  import org.kuali.rice.kns.web.ui.Field;
30  import org.kuali.rice.kns.web.ui.Row;
31  
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  
40  /**
41   * This class adapts the RemotableAttributeField instances from the various attributes
42   * associated with a document type and combines with the "default" rows for the search,
43   * returning the final List of Row objects to render for the document search.
44   *
45   * <p>Implementation note:</p>
46   * <p>
47   * This implementation relies on applicationDocumentStatus, and dateApplicationDocumentStatusChanged conditional fields
48   * being defined in the DD for basic display purposes.  These fields are conditionally shown depending on whether
49   * a document supporting application document statuses has been specified.  Further, the applicationDocumentStatus field
50   * is dynamically switched to a dropdown when the document specifies an explicit enumeration of valid statuses (this
51   * control switching is something that is not possible via declarative DD, at the time of this writing).
52   * </p>
53   * <p>
54   * In addition the routeNodeName field is dynamically populated with the list of route nodes for the specified document
55   * type.
56   * <p>
57   * Note: an alternative to programmatically providing dynamic select values is to define a value finder declaratively in
58   * DD.  KeyValueFinder however does not have access to request state, including the required document type, which would mean
59   * resorting to GlobalVariables inspection.  In reluctance to add yet another dependency on this external state, the fixups
60   * are done programmatically in this class. (see {@link #applyApplicationDocumentStatusCustomizations(org.kuali.rice.kns.web.ui.Field, org.kuali.rice.kew.doctype.bo.DocumentType)},
61   * {@link #applyRouteNodeNameCustomizations(org.kuali.rice.kns.web.ui.Field, org.kuali.rice.kew.doctype.bo.DocumentType)}).
62   * </p>
63   * @author Kuali Rice Team (rice.collab@kuali.org)
64   *
65   */
66  public class DocumentSearchCriteriaProcessorKEWAdapter implements DocumentSearchCriteriaProcessor {
67      /**
68       * Name if the hidden input field containing basic/detailed search toggle state
69       */
70      public static final String ADVANCED_SEARCH_FIELD = "isAdvancedSearch";
71      /**
72       * Name if the hidden input field containing non-superuser/superuser search toggle state
73       */
74      public static final String SUPERUSER_SEARCH_FIELD = "superUserSearch";
75      /**
76       * Name if the hidden input field containing the clear saved search flag
77       */
78      public static final String CLEARSAVED_SEARCH_FIELD = "resetSavedSearch";
79  
80      /**
81       * Indicates where document attributes should be placed inside search criteria
82       */
83      private static final String DOCUMENT_ATTRIBUTE_FIELD_MARKER = "DOCUMENT_ATTRIBUTE_FIELD_MARKER";
84  
85      private static final String APPLICATION_DOCUMENT_STATUS = "applicationDocumentStatus";
86      private static final String DATE_APP_DOC_STATUS_CHANGED_FROM = "rangeLowerBoundKeyPrefix_dateApplicationDocumentStatusChanged";
87      private static final String DATE_APP_DOC_STATUS_CHANGED = "dateApplicationDocumentStatusChanged";
88      private static final String ROUTE_NODE_NAME = "routeNodeName";
89      private static final String ROUTE_NODE_LOGIC = "routeNodeLogic";
90  
91      private static final String[] BASIC_FIELD_NAMES = {
92              "documentTypeName",
93              "initiatorPrincipalName",
94              "documentId",
95              "groupViewerName",
96              "dateCreated",
97              DOCUMENT_ATTRIBUTE_FIELD_MARKER,
98              "saveName"
99      };
100 
101     private static final String[] ADVANCED_FIELD_NAMES = {
102             "documentTypeName",
103             "initiatorPrincipalName",
104             "approverPrincipalName",
105             "viewerPrincipalName",
106             "groupViewerName",
107             "groupViewerId",
108             "documentId",
109             "applicationDocumentId",
110             "statusCode",
111             APPLICATION_DOCUMENT_STATUS,
112             DATE_APP_DOC_STATUS_CHANGED,
113             ROUTE_NODE_NAME,
114             ROUTE_NODE_LOGIC,
115             "dateCreated",
116             "dateApproved",
117             "dateLastModified",
118             "dateFinalized",
119             "title",
120             DOCUMENT_ATTRIBUTE_FIELD_MARKER,
121             "saveName"
122     };
123 
124     /**
125      * Fields that are only applicable if a document type has been specified
126      */
127     private static final Collection<String> DOCUMENTTYPE_DEPENDENT_FIELDS = Arrays.asList(new String[] { DOCUMENT_ATTRIBUTE_FIELD_MARKER, APPLICATION_DOCUMENT_STATUS, DATE_APP_DOC_STATUS_CHANGED_FROM, DATE_APP_DOC_STATUS_CHANGED, ROUTE_NODE_NAME, ROUTE_NODE_LOGIC });
128     /**
129      * Fields that are only applicable if application document status is in use (assumes documenttype dependency)
130      */
131     private static final Collection<String> DOCSTATUS_DEPENDENT_FIELDS = Arrays.asList(new String[] { APPLICATION_DOCUMENT_STATUS, DATE_APP_DOC_STATUS_CHANGED_FROM, DATE_APP_DOC_STATUS_CHANGED });
132 
133 
134     @Override
135     public List<Row> getRows(DocumentType documentType, List<Row> defaultRows, boolean advancedSearch, boolean superUserSearch) {
136         List<Row> rows = null;
137         if(advancedSearch) {
138             rows = loadRowsForAdvancedSearch(defaultRows, documentType);
139         } else {
140             rows = loadRowsForBasicSearch(defaultRows, documentType);
141         }
142         addHiddenFields(rows, advancedSearch, superUserSearch);
143         return rows;
144     }
145 
146     protected List<Row> loadRowsForAdvancedSearch(List<Row> defaultRows, DocumentType documentType) {
147         List<Row> rows = new ArrayList<Row>();
148         loadRowsWithFields(rows, defaultRows, ADVANCED_FIELD_NAMES, documentType);
149         return rows;
150     }
151 
152     protected List<Row> loadRowsForBasicSearch(List<Row> defaultRows, DocumentType documentType) {
153         List<Row> rows = new ArrayList<Row>();
154         loadRowsWithFields(rows, defaultRows, BASIC_FIELD_NAMES, documentType);
155         return rows;
156     }
157 
158     /**
159      * Generates the document search form fields given the DataDictionary-defined fields, the DocumentType,
160      * and whether basic, detailed, or superuser search is being rendered.
161      * If the document type policy DOCUMENT_STATUS_POLICY is set to "app", or "both"
162      * Then display the doc search criteria fields.
163      * If the documentType.validApplicationStatuses are defined, then the criteria field is a drop down.
164      * If the validApplication statuses are NOT defined, then the criteria field is a text input.
165      * @param rowsToLoad the list of rows to update
166      * @param defaultRows the DataDictionary-derived default form rows
167      * @param fieldNames a list of field names corresponding to the fields to render according to the current document search state
168      * @param documentType the document type, if specified in the search form
169      */
170     protected void loadRowsWithFields(List<Row> rowsToLoad, List<Row> defaultRows, String[] fieldNames,
171             DocumentType documentType) {
172 
173         for (String fieldName : fieldNames) {
174             if (DOCUMENTTYPE_DEPENDENT_FIELDS.contains(fieldName) && documentType == null) {
175                 continue;
176             }
177             // assuming DOCSTATUS_DEPENDENT_FIELDS are also documentType-dependent, this block is only executed when documentType is present
178             if (DOCSTATUS_DEPENDENT_FIELDS.contains(fieldName) && !documentType.isAppDocStatusInUse()) {
179                 continue;
180             }
181             if (fieldName.equals(DOCUMENT_ATTRIBUTE_FIELD_MARKER)) {
182                 rowsToLoad.addAll(getDocumentAttributeRows(documentType));
183                 continue;
184             }
185             // now add all matching rows given
186             // 1) the field is doc type and doc status independent
187             // 2) the field is doc type dependent and the doctype is specified
188             // 3) the field is doc status dependent and the doctype is specified and doc status is in use
189             for (Row row : defaultRows) {
190                 boolean matched = false;
191                 // we must iterate over each field without short-circuiting to make sure to inspect the
192                 // APPLICATION_DOCUMENT_STATUS field, which needs customizations
193                 for (Field field : row.getFields()) {
194                     // dp "endsWith" here because lower bounds properties come
195                     // across like "rangeLowerBoundKeyPrefix_dateCreated"
196                     if (field.getPropertyName().equals(fieldName) || field.getPropertyName().endsWith("_" + fieldName)) {
197                         matched = true;
198                         if (APPLICATION_DOCUMENT_STATUS.equals(field.getPropertyName())) {
199                             // If Application Document Status policy is in effect for this document type,
200                             // add search attributes for document status, and transition dates.
201                             // Note: document status field is a drop down if valid statuses are defined, a text input field otherwise.
202                             applyApplicationDocumentStatusCustomizations(field, documentType);
203                             break;
204                         } else if (ROUTE_NODE_NAME.equals(field.getPropertyName())) {
205                             // populates routenodename dropdown with documenttype nodes
206                             applyRouteNodeNameCustomizations(field, documentType);
207                         }
208                     }
209                 }
210                 if (matched) {
211                     rowsToLoad.add(row);
212                 }
213             }
214         }
215     }
216 
217     /**
218      * Returns fields for the search attributes defined on the document
219      */
220     protected List<Row> getDocumentAttributeRows(DocumentType documentType) {
221         List<Row> documentAttributeRows = new ArrayList<Row>();
222         DocumentSearchCriteriaConfiguration configuration =
223                 KEWServiceLocator.getDocumentSearchCustomizationMediator().
224                         getDocumentSearchCriteriaConfiguration(documentType);
225         if (configuration != null) {
226             List<RemotableAttributeField> remotableAttributeFields = configuration.getFlattenedSearchAttributeFields();
227             if (remotableAttributeFields != null && !remotableAttributeFields.isEmpty()) {
228                 documentAttributeRows.addAll(FieldUtils.convertRemotableAttributeFields(remotableAttributeFields));
229             }
230         }
231         List<Row> fixedDocumentAttributeRows = new ArrayList<Row>();
232         for (Row row : documentAttributeRows) {
233             List<Field> fields = row.getFields();
234             for (Field field : fields) {
235                 //force the max length for now if not set
236                 if(field.getMaxLength() == 0) {
237                     field.setMaxLength(100);
238                 }
239                 if(field.isDatePicker() && field.isRanged()) {
240                     Field newDate = FieldUtils.createRangeDateField(field);
241                     List<Field> newFields = new ArrayList<Field>();
242                     newFields.add(newDate);
243                     fixedDocumentAttributeRows.addAll(FieldUtils.wrapFields(newFields));
244                 }
245                 // prepend all document attribute field names with "documentAttribute."
246                 field.setPropertyName(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + field.getPropertyName());
247             }
248             fixedDocumentAttributeRows.add(row);
249         }
250 
251         return fixedDocumentAttributeRows;
252     }
253 
254     /**
255      * Modifies the DataDictionary-defined applicationDocumentStatus field control to reflect whether the DocumentType
256      * has specified a list of valid application document statuses (in which case a select control is rendered), or whether
257      * it is free form (in which case a text control is rendered)
258      *
259      * @param field the applicationDocumentStatus field
260      * @param documentType the document type
261      */
262     protected void applyApplicationDocumentStatusCustomizations(Field field, DocumentType documentType) {
263         List<ApplicationDocumentStatus> validStatuses = documentType.getValidApplicationStatuses();
264         if (validStatuses == null || validStatuses.size() == 0){
265             // use a text input field
266             // StandardSearchCriteriaField(String fieldKey, String propertyName, String fieldType, String datePickerKey, String labelMessageKey, String helpMessageKeyArgument, boolean hidden, String displayOnlyPropertyName, String lookupableImplServiceName, boolean lookupTypeRequired)
267             // new StandardSearchCriteriaField(DocumentSearchCriteriaProcessor.CRITERIA_KEY_APP_DOC_STATUS,"criteria.appDocStatus",StandardSearchCriteriaField.TEXT,null,null,"DocSearchApplicationDocStatus",false,null,null,false));
268             // String fieldKey DocumentSearchCriteriaProcessor.CRITERIA_KEY_APP_DOC_STATUS
269             field.setFieldType(Field.TEXT);
270         } else {
271             // dropdown
272             // String fieldKey DocumentSearchCriteriaProcessor.CRITERIA_KEY_APP_DOC_STATUS + "_VALUES"
273             field.setFieldType(Field.DROPDOWN);
274             List<KeyValue> validValues = new ArrayList<KeyValue>();
275             for (ApplicationDocumentStatus status: validStatuses) {
276                 validValues.add(new ConcreteKeyValue(status.getStatusName(), status.getStatusName()));
277             }
278             field.setFieldValidValues(validValues);
279             //dropDown.setOptionsCollectionProperty("validApplicationStatuses");
280             //dropDown.setCollectionKeyProperty("statusName");
281             //dropDown.setCollectionLabelProperty("statusName");
282             //dropDown.setEmptyCollectionMessage("Select a document status.");
283         }
284     }
285 
286     protected void applyRouteNodeNameCustomizations(Field field, DocumentType documentType) {
287         List<RouteNode> nodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(documentType, true);
288         List<KeyValue> values = new ArrayList<KeyValue>(nodes.size());
289         for (RouteNode node: nodes) {
290             values.add(new ConcreteKeyValue(node.getName(), node.getName()));
291         }
292         field.setFieldValidValues(values);
293     }
294 
295     protected void addHiddenFields(List<Row> rows, boolean advancedSearch, boolean superUserSearch) {
296         Row hiddenRow = new Row();
297         hiddenRow.setHidden(true);
298 
299         Field detailedField = new Field();
300         detailedField.setPropertyName(ADVANCED_SEARCH_FIELD);
301         detailedField.setPropertyValue(advancedSearch ? "YES" : "NO");
302         detailedField.setFieldType(Field.HIDDEN);
303 
304         Field superUserSearchField = new Field();
305         superUserSearchField.setPropertyName(SUPERUSER_SEARCH_FIELD);
306         superUserSearchField.setPropertyValue(superUserSearch ? "YES" : "NO");
307         superUserSearchField.setFieldType(Field.HIDDEN);
308 
309         Field clearSavedSearchField = new Field();
310         clearSavedSearchField .setPropertyName(CLEARSAVED_SEARCH_FIELD);
311         clearSavedSearchField .setPropertyValue(superUserSearch ? "YES" : "NO");
312         clearSavedSearchField .setFieldType(Field.HIDDEN);
313 
314         List<Field> hiddenFields = new ArrayList<Field>();
315         hiddenFields.add(detailedField);
316         hiddenFields.add(superUserSearchField);
317         hiddenFields.add(clearSavedSearchField);
318         hiddenRow.setFields(hiddenFields);
319         rows.add(hiddenRow);
320 
321     }
322 
323 }