001/**
002 * Copyright 2005-2018 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.krad.lookup;
017
018import java.util.HashMap;
019import java.util.Map;
020
021import org.apache.commons.lang.StringUtils;
022import org.kuali.rice.core.api.util.ConcreteKeyValue;
023import org.kuali.rice.krad.datadictionary.AttributeDefinition;
024import org.kuali.rice.krad.datadictionary.parse.BeanTag;
025import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
026import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
027import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
028import org.kuali.rice.krad.uif.UifConstants;
029import org.kuali.rice.krad.uif.UifPropertyPaths;
030import org.kuali.rice.krad.uif.control.CheckboxControl;
031import org.kuali.rice.krad.uif.control.Control;
032import org.kuali.rice.krad.uif.control.FilterableLookupCriteriaControl;
033import org.kuali.rice.krad.uif.control.MultiValueControl;
034import org.kuali.rice.krad.uif.control.RadioGroupControl;
035import org.kuali.rice.krad.uif.control.TextAreaControl;
036import org.kuali.rice.krad.uif.element.Message;
037import org.kuali.rice.krad.uif.field.InputField;
038import org.kuali.rice.krad.uif.field.InputFieldBase;
039import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
040import org.kuali.rice.krad.uif.util.ComponentFactory;
041import org.kuali.rice.krad.uif.util.ComponentUtils;
042import org.kuali.rice.krad.uif.util.KeyMessage;
043import org.kuali.rice.krad.uif.util.LifecycleElement;
044import org.kuali.rice.krad.util.KRADConstants;
045import org.kuali.rice.krad.util.KRADPropertyConstants;
046
047/**
048 * Custom {@link InputField} for criteria fields within a lookup view that adds criteria specific options.
049 *
050 * @author Kuali Rice Team (rice.collab@kuali.org)
051 */
052@BeanTag(name = "lookupCriteria", parent = "Uif-LookupCriteriaInputField")
053public class LookupInputField extends InputFieldBase {
054    private static final long serialVersionUID = -8294275596836322699L;
055
056    private boolean disableWildcardsAndOperators;
057    private boolean addControlSelectAllOption;
058    private boolean ranged;
059
060    public LookupInputField() {
061        super();
062    }
063
064    /**
065     * The following actions are performed:
066     *
067     * <ul>
068     * <li>Add all option if enabled and control is multi-value</li>
069     * </ul>
070     *
071     * {@inheritDoc}
072     */
073    @Override
074    public void performFinalize(Object model, LifecycleElement parent) {
075        super.performFinalize(model, parent);
076
077        // if enabled add option to select all values
078        if (addControlSelectAllOption && (getControl() != null) && getControl() instanceof MultiValueControl) {
079            String allOptionText = KRADServiceLocatorWeb.getMessageService().getMessageText(
080                    UifConstants.MessageKeys.OPTION_ALL);
081
082            MultiValueControl multiValueControl = (MultiValueControl) getControl();
083            if (multiValueControl.getOptions() != null) {
084                multiValueControl.getOptions().add(0, new ConcreteKeyValue("", allOptionText));
085            }
086
087            if (multiValueControl.getRichOptions() != null) {
088                Message message = ComponentFactory.getMessage();
089                message.setMessageText(allOptionText);
090                message.setRenderWrapperTag(false);
091
092                multiValueControl.getRichOptions().add(0, new KeyMessage("", allOptionText, message));
093            }
094        }
095    }
096
097    /**
098     * Invoked during the finalize phase to capture state of the component needs to support post operations.
099     */
100    @Override
101    protected void addComponentPostMetadata() {
102        super.addComponentPostMetadata();
103
104        Map<String, Map<String, Object>> lookupCriteriaFields = ViewLifecycle.getViewPostMetadata().getLookupCriteria();
105
106        Map<String, Object> criteriaAttributes = new HashMap<String, Object>();
107        criteriaAttributes.put(UifConstants.LookupCriteriaPostMetadata.COMPONENT_ID, this.getId());
108
109        if (this.isDisableWildcardsAndOperators()) {
110            criteriaAttributes.put(UifConstants.LookupCriteriaPostMetadata.DISABLE_WILDCARDS_AND_OPERATORS, true);
111        }
112
113        if (this.getRequired()) {
114            criteriaAttributes.put(UifConstants.LookupCriteriaPostMetadata.REQUIRED, true);
115        }
116
117        if (this.hasSecureValue()) {
118            criteriaAttributes.put(UifConstants.LookupCriteriaPostMetadata.SECURE_VALUE, true);
119        }
120
121        ValidCharactersConstraint validCharactersConstraint = this.getValidCharactersConstraint();
122        if (validCharactersConstraint != null) {
123            criteriaAttributes.put(UifConstants.LookupCriteriaPostMetadata.VALID_CHARACTERS_CONSTRAINT,
124                    validCharactersConstraint);
125        }
126
127        lookupCriteriaFields.put(this.getPropertyName(), criteriaAttributes);
128
129        addHiddenComponentPostMetadata(lookupCriteriaFields);
130    }
131
132    /**
133     * Add hidden search criteria components.
134     *
135     * @param lookupCriteriaFields
136     */
137    protected void addHiddenComponentPostMetadata(Map<String, Map<String, Object>> lookupCriteriaFields) {
138        for (String hiddenPropertyName: this.getAdditionalHiddenPropertyNames()) {
139            hiddenPropertyName = StringUtils.substringBetween(hiddenPropertyName, UifPropertyPaths.LOOKUP_CRITERIA + "[", "]");
140
141            // Prevent overwriting of visible components. Note, hidden components are allowed to be overwritten.
142            if (!lookupCriteriaFields.containsKey(hiddenPropertyName)) {
143                Map<String, Object> criteriaAttributes = new HashMap<String, Object>();
144                criteriaAttributes.put(UifConstants.LookupCriteriaPostMetadata.HIDDEN, true);
145                lookupCriteriaFields.put(hiddenPropertyName, criteriaAttributes);
146            }
147        }
148    }
149
150    /**
151     * Override of InputField copy to setup properties necessary to make the field usable for inputting
152     * search criteria.
153     *
154     * <p>Note super is not being called because we don't want to add restirctions that can cause problems
155     * with the use of wildcard</p>
156     *
157     * {@inheritDoc}
158     */
159    @Override
160    public void copyFromAttributeDefinition(AttributeDefinition attributeDefinition) {
161        // label
162        if (StringUtils.isEmpty(getLabel())) {
163            setLabel(attributeDefinition.getLabel());
164        }
165
166        // short label
167        if (StringUtils.isEmpty(getShortLabel())) {
168            setShortLabel(attributeDefinition.getShortLabel());
169        }
170
171        // security
172        if ((attributeDefinition.getAttributeSecurity() != null) && ((getDataFieldSecurity() == null) || (
173                getDataFieldSecurity().getAttributeSecurity() == null))) {
174            initializeComponentSecurity();
175
176            getDataFieldSecurity().setAttributeSecurity(attributeDefinition.getAttributeSecurity());
177        }
178
179        // options
180        if (getOptionsFinder() == null) {
181            setOptionsFinder(attributeDefinition.getOptionsFinder());
182        }
183
184        // use control from dictionary if not specified and convert for searching
185        if (getControl() == null) {
186            Control control = convertControlToLookupControl(attributeDefinition);
187            setControl(control);
188        }
189
190        // overwrite maxLength to allow for wildcards and ranges; set a minimum max length unless it is greater than 100
191        setMaxLength(100);
192        if ( attributeDefinition.getMaxLength()!=null && (attributeDefinition.getMaxLength() > 100)) {
193            setMaxLength(attributeDefinition.getMaxLength());
194        }
195
196        // set default value for active field to true
197        if (getDefaultValue() == null || (getDefaultValue() instanceof String && StringUtils.isEmpty((String)getDefaultValue()))) {
198            if ((StringUtils.equals(getPropertyName(), KRADPropertyConstants.ACTIVE))) {
199                setDefaultValue(KRADConstants.YES_INDICATOR_VALUE);
200            }
201        }
202    }
203
204    /**
205     * If control definition is defined on the given attribute definition, converts to an appropriate control for
206     * searching (if necessary) and returns a copy for setting on the field.
207     *
208     * @param attributeDefinition attribute definition instance to retrieve control from
209     * @return Control instance or null if not found
210     */
211    protected static Control convertControlToLookupControl(AttributeDefinition attributeDefinition) {
212        if (attributeDefinition.getControlField() == null) {
213            return null;
214        }
215
216        Control newControl = null;
217
218        // convert checkbox to radio with yes/no/both options
219        if (CheckboxControl.class.isAssignableFrom(attributeDefinition.getControlField().getClass())) {
220            newControl = (RadioGroupControl) ComponentFactory.getNewComponentInstance(
221                    ComponentFactory.CHECKBOX_CONVERTED_RADIO_CONTROL);
222        }
223        // text areas get converted to simple text inputs
224        else if (TextAreaControl.class.isAssignableFrom(attributeDefinition.getControlField().getClass())) {
225            newControl = ComponentFactory.getTextControl();
226        } else {
227            newControl = ComponentUtils.copy(attributeDefinition.getControlField(), "");
228        }
229
230        return newControl;
231    }
232
233    /**
234     * Invoked before search is carried out to perform any necessary filtering of the criteria.
235     *
236     * @param searchCriteria the search criteria to be filtered
237     * @return map of filtered search criteria
238     */
239    public Map<String, String> filterSearchCriteria(Map<String, String> searchCriteria) {
240        return searchCriteria;
241    }
242
243    /**
244     * Indicates whether wildcard and other search operators should be disabled (treated as literals) for
245     * the input field.
246     *
247     * @return boolean true if wildcards and search operators should be disabled, false if enabled
248     */
249    @BeanTagAttribute(name = "disableWildcardsAndOperators")
250    public boolean isDisableWildcardsAndOperators() {
251        return this.disableWildcardsAndOperators;
252    }
253
254    /**
255     * @see LookupInputField#isDisableWildcardsAndOperators()
256     */
257    public void setDisableWildcardsAndOperators(boolean disableWildcardsAndOperators) {
258        this.disableWildcardsAndOperators = disableWildcardsAndOperators;
259    }
260
261    /**
262     * Indicates whether the option for all values (blank key, 'All' label) should be added to the lookup
263     * field, note this is only supported for {@link org.kuali.rice.krad.uif.control.MultiValueControl} instance.
264     *
265     * @return boolean true if all option should be added, false if not
266     */
267    @BeanTagAttribute(name = "addControlSelectAllOption")
268    public boolean isAddControlSelectAllOption() {
269        return addControlSelectAllOption;
270    }
271
272    /**
273     * @see LookupInputField#isAddControlSelectAllOption()
274     */
275    public void setAddControlSelectAllOption(boolean addControlSelectAllOption) {
276        this.addControlSelectAllOption = addControlSelectAllOption;
277    }
278
279    /**
280     * Indicates a field group should be created containing a from and to input field for date search
281     * ranges.
282     *
283     * <p>
284     * When this is set to true, the input field will be replaced by a field group that is created by
285     * copying the prototype {@link org.kuali.rice.krad.lookup.LookupView#getRangeFieldGroupPrototype()}. Within the
286     * field group, an lookup input field will be created for the from field, and this input will be used
287     * as the to date field. Between the two fields a message will be rendered that can be specified using
288     * {@link LookupView#getRangedToMessage()}
289     * </p>
290     *
291     * @return boolean true if ranged field group should be created, false if not
292     */
293    public boolean isRanged() {
294        return ranged;
295    }
296
297    /**
298     * @see LookupInputField#isRanged()
299     */
300    public void setRanged(boolean ranged) {
301        this.ranged = ranged;
302    }
303}