org.kuali.rice.krad.uif.widget.Inquiry.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.krad.uif.widget.Inquiry.java

Source

/**
 * Copyright 2005-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.krad.uif.widget;

import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import com.sun.corba.se.impl.encoding.OSFCodeSetRegistry;
import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.api.CoreApiServiceLocator;
import org.kuali.rice.krad.datadictionary.parse.BeanTag;
import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
import org.kuali.rice.krad.messages.MessageService;
import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
import org.kuali.rice.krad.service.ModuleService;
import org.kuali.rice.krad.uif.UifConstants;
import org.kuali.rice.krad.uif.UifParameters;
import org.kuali.rice.krad.uif.component.BindingInfo;
import org.kuali.rice.krad.uif.component.Component;
import org.kuali.rice.krad.uif.element.Action;
import org.kuali.rice.krad.uif.element.Link;
import org.kuali.rice.krad.uif.field.DataField;
import org.kuali.rice.krad.uif.field.InputField;
import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
import org.kuali.rice.krad.uif.util.LifecycleElement;
import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
import org.kuali.rice.krad.uif.util.ViewModelUtils;
import org.kuali.rice.krad.uif.view.View;
import org.kuali.rice.krad.util.KRADUtils;
import org.kuali.rice.krad.util.UrlFactory;
import org.kuali.rice.krad.web.form.InquiryForm;
import org.kuali.rice.krad.web.form.UifFormBase;

/**
 * Widget for rendering an Inquiry link or DirectInquiry action field
 *
 * <p>
 * The inquiry widget will render a button for the field value when
 * that field is editable. When read only the widget will create a link on the display value.
 * It points to the associated inquiry view for the field. The inquiry can be configured to point to a certain
 * {@code InquiryView}, or the framework will attempt to associate the field with a inquiry based on
 * its metadata (in particular its relationships in the model).
 * </p>
 *
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
@BeanTag(name = "inquiry", parent = "Uif-Inquiry")
public class Inquiry extends WidgetBase {
    private static final long serialVersionUID = -2154388007867302901L;
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(Inquiry.class);

    public static final String INQUIRY_TITLE_PREFIX = "title.inquiry.url.actiontext";
    public static final String INQUIRY_TITLE_POSTFIX = "title.inquiry.url.value.prependtext";

    private String baseInquiryUrl;

    private String dataObjectClassName;
    private String viewName;

    private Map<String, String> inquiryParameters;

    private Link inquiryLink;

    private Action directInquiryAction;
    private boolean enableDirectInquiry;

    private boolean adjustInquiryParameters;
    private BindingInfo fieldBindingInfo;

    private boolean parentReadOnly;

    public Inquiry() {
        super();

        inquiryParameters = new HashMap<String, String>();
    }

    /**
     * Inherits readOnly from parent if not explicitly populated.
     * 
     * {@inheritDoc}
     */
    @Override
    public void afterEvaluateExpression() {
        super.afterEvaluateExpression();

        if (getReadOnly() == null) {
            Component parent = ViewLifecycle.getPhase().getParent();
            setReadOnly(parent == null ? null : parent.getReadOnly());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void performFinalize(Object model, LifecycleElement parent) {
        super.performFinalize(model, parent);

        if (!isRender()) {
            return;
        }

        // set render to false until we find an inquiry class
        setRender(false);

        // used to determine whether a normal or direct inquiry should be enabled
        if (parent instanceof Component) {
            setParentReadOnly(((Component) parent).getReadOnly());
        }

        // Do checks for inquiry when read only
        if (isParentReadOnly()) {
            if (StringUtils.isBlank(((DataField) parent).getBindingInfo().getBindingPath())
                    || ((DataField) parent).getBindingInfo().getBindingPath().equals("null")) {
                return;
            }

            // check if field value is null, if so no inquiry
            try {
                Object propertyValue = ObjectPropertyUtils.getPropertyValue(model,
                        ((DataField) parent).getBindingInfo().getBindingPath());

                if ((propertyValue == null) || StringUtils.isBlank(propertyValue.toString())) {
                    return;
                }
            } catch (Exception e) {
                // if we can't get the value just swallow the exception and don't set an inquiry
                return;
            }

            View view = ViewLifecycle.getActiveLifecycle().getView();
            // skips creating inquiry link if same as parent
            if (view.getViewTypeName() == UifConstants.ViewType.INQUIRY) {
                DataField dataField = (DataField) parent;
                InquiryForm inquiryForm = (InquiryForm) model;

                // value of field
                Object fieldValue = ObjectPropertyUtils.getPropertyValue(
                        ViewModelUtils.getParentObjectForMetadata(view, model, dataField),
                        dataField.getPropertyName());

                // value of field in request parameter
                Object parameterValue = inquiryForm.getInitialRequestParameters().get(dataField.getPropertyName());

                // if data classes and field values are equal
                if (inquiryForm.getDataObjectClassName().equals(dataField.getDictionaryObjectEntry())
                        && parameterValue != null && fieldValue.equals(parameterValue)) {
                    return;
                }
            }
        }

        // Do checks for direct inquiry when editable
        if (!isParentReadOnly() && parent instanceof InputField) {
            if (!enableDirectInquiry) {
                return;
            }

            // determine whether inquiry parameters will need adjusted
            if (StringUtils.isBlank(getDataObjectClassName()) || (getInquiryParameters() == null)
                    || getInquiryParameters().isEmpty()) {
                // if inquiry parameters not given, they will not be adjusted by super
                adjustInquiryParameters = true;
                fieldBindingInfo = ((InputField) parent).getBindingInfo();
            }
        }

        setupLink(model, (DataField) parent);

        if (isRender() && !isParentReadOnly() && enableDirectInquiry) {
            ((InputField) parent).addPostInputAddon(directInquiryAction);
        }
    }

    /**
     * Get parent object and field name and build the inquiry link
     *
     * <p>
     * This was moved from the performFinalize because overlapping and to be used
     * by DirectInquiry.
     * </p>
     *
     * @param model model
     * @param field The parent Attribute field
     */
    private void setupLink(Object model, DataField field) {
        String propertyName = field.getBindingInfo().getBindingName();

        // if class and parameters configured, build link from those
        if (StringUtils.isNotBlank(getDataObjectClassName()) && (getInquiryParameters() != null)
                && !getInquiryParameters().isEmpty()) {
            Class<?> inquiryObjectClass;
            try {
                inquiryObjectClass = Class.forName(getDataObjectClassName());
            } catch (ClassNotFoundException e) {
                LOG.error("Unable to get class for: " + getDataObjectClassName());
                throw new RuntimeException(e);
            }

            updateInquiryParameters(field.getBindingInfo());

            buildInquiryLink(model, propertyName, inquiryObjectClass, getInquiryParameters());
        }
        // get inquiry class and parameters from view helper
        else {
            // get parent object for inquiry metadata
            ViewLifecycle viewLifecycle = ViewLifecycle.getActiveLifecycle();

            Object parentObject = ViewModelUtils.getParentObjectForMetadata(viewLifecycle.getView(), model, field);
            if (parentObject != null) {
                viewLifecycle.getHelper().buildInquiryLink(parentObject, propertyName, this);
            }
        }
    }

    /**
     * Adjusts the path on the inquiry parameter property to match the binding
     * path prefix of the given {@code BindingInfo}
     *
     * @param bindingInfo binding info instance to copy binding path prefix from
     */
    public void updateInquiryParameters(BindingInfo bindingInfo) {
        Map<String, String> adjustedInquiryParameters = new HashMap<String, String>();
        for (Entry<String, String> stringEntry : inquiryParameters.entrySet()) {
            String toField = stringEntry.getValue();
            String adjustedFromFieldPath = bindingInfo.getPropertyAdjustedBindingPath(stringEntry.getKey());

            adjustedInquiryParameters.put(adjustedFromFieldPath, toField);
        }

        this.inquiryParameters = adjustedInquiryParameters;
    }

    /**
     * Builds the inquiry link based on the given inquiry class and parameters
     *
     * @param dataObject parent object that contains the data (used to pull inquiry
     * parameters)
     * @param propertyName name of the property the inquiry is set on
     * @param inquiryObjectClass class of the object the inquiry should point to
     * @param inquiryParams map of key field mappings for the inquiry
     */
    @SuppressWarnings("deprecation")
    public void buildInquiryLink(Object dataObject, String propertyName, Class<?> inquiryObjectClass,
            Map<String, String> inquiryParams) {
        MessageService messageService = KRADServiceLocatorWeb.getMessageService();

        Properties urlParameters = new Properties();
        Map<String, String> inquiryKeyValues = new HashMap<String, String>();

        urlParameters.setProperty(UifParameters.DATA_OBJECT_CLASS_NAME, inquiryObjectClass.getName());
        urlParameters.setProperty(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
        if (StringUtils.isNotBlank(this.viewName)) {
            urlParameters.setProperty(UifParameters.VIEW_NAME, this.viewName);
        }

        // add inquiry specific parms to url
        if (getInquiryLink().getLightBox() != null) {
            getInquiryLink().getLightBox().setAddAppParms(true);
        }

        // configure inquiry when read only
        if (isParentReadOnly()) {
            for (Entry<String, String> inquiryParameter : inquiryParams.entrySet()) {
                String parameterName = inquiryParameter.getKey();

                Object parameterValue = ObjectPropertyUtils.getPropertyValue(dataObject, parameterName);

                // TODO: need general format util that uses spring
                if (parameterValue == null) {
                    parameterValue = "";
                } else if (parameterValue instanceof java.sql.Date) {
                    if (org.kuali.rice.core.web.format.Formatter.findFormatter(parameterValue.getClass()) != null) {
                        org.kuali.rice.core.web.format.Formatter formatter = org.kuali.rice.core.web.format.Formatter
                                .getFormatter(parameterValue.getClass());
                        parameterValue = formatter.format(parameterValue);
                    }
                } else {
                    parameterValue = ObjectPropertyUtils.getPropertyValueAsText(dataObject, parameterName);
                }

                // Encrypt value if it is a field that has restriction that prevents a value from being shown to
                // user, because we don't want the browser history to store the restricted attributes value in the URL
                if (KRADServiceLocatorWeb.getDataObjectAuthorizationService()
                        .attributeValueNeedsToBeEncryptedOnFormsAndLinks(inquiryObjectClass,
                                inquiryParameter.getValue())) {
                    try {
                        parameterValue = CoreApiServiceLocator.getEncryptionService().encrypt(parameterValue);
                    } catch (GeneralSecurityException e) {
                        throw new RuntimeException(
                                "Exception while trying to encrypted value for inquiry framework.", e);
                    }
                }

                // add inquiry parameter to URL
                urlParameters.put(inquiryParameter.getValue(), parameterValue);

                inquiryKeyValues.put(inquiryParameter.getValue(), parameterValue.toString());
            }

            /* build inquiry URL */
            String inquiryUrl;

            // check for EBOs for an alternate inquiry URL
            ModuleService responsibleModuleService = KRADServiceLocatorWeb.getKualiModuleService()
                    .getResponsibleModuleService(inquiryObjectClass);
            if (responsibleModuleService != null && responsibleModuleService.isExternalizable(inquiryObjectClass)) {
                inquiryUrl = responsibleModuleService.getExternalizableDataObjectInquiryUrl(inquiryObjectClass,
                        urlParameters);
            } else {
                inquiryUrl = UrlFactory.parameterizeUrl(getBaseInquiryUrl(), urlParameters);
            }

            getInquiryLink().setHref(inquiryUrl);

            // set inquiry title
            getInquiryLink().setTitle(createTitleText(inquiryObjectClass, inquiryKeyValues));

            setRender(true);
        }
        // configure direct inquiry when editable
        else {
            // Direct inquiry
            String inquiryUrl = UrlFactory.parameterizeUrl(getBaseInquiryUrl(), urlParameters);

            StringBuilder paramMapStringBuilder = new StringBuilder();

            // Build parameter string using the actual names of the fields as on the html page
            for (Entry<String, String> inquiryParameter : inquiryParams.entrySet()) {
                String inquiryParameterFrom = inquiryParameter.getKey();

                if (adjustInquiryParameters && (fieldBindingInfo != null)) {
                    inquiryParameterFrom = fieldBindingInfo.getPropertyAdjustedBindingPath(inquiryParameterFrom);
                }

                ViewLifecycle viewLifecycle = ViewLifecycle.getActiveLifecycle();

                // Make sure our inquiry parameters are included as a rendered property path
                if (!viewLifecycle.getViewPostMetadata().getAllRenderedPropertyPaths()
                        .contains(inquiryParameterFrom.toString())) {
                    setRender(false);
                    return;
                }

                paramMapStringBuilder.append(inquiryParameterFrom);
                paramMapStringBuilder.append(":");
                paramMapStringBuilder.append(inquiryParameter.getValue());
                paramMapStringBuilder.append(",");

            }
            String paramMapString = StringUtils.removeEnd(paramMapStringBuilder.toString(), ",");

            // Check if lightbox is set. Get lightbox options.
            String lightBoxOptions = "";
            boolean lightBoxShow = (getInquiryLink().getLightBox() != null);
            if (lightBoxShow) {
                lightBoxOptions = getInquiryLink().getLightBox().getTemplateOptionsJSString();
            } else {
                String title = this.getTitle();
                if (StringUtils.isNotBlank(title)) {
                    this.setTitle(title + " - " + messageService.getMessageText("accessibility.link.opensTab"));
                } else {
                    this.setTitle(messageService.getMessageText("accessibility.link.opensTab"));
                }
            }

            // Create onlick script to open the inquiry window on the click event
            // of the direct inquiry
            StringBuilder onClickScript = new StringBuilder("showDirectInquiry(\"");
            onClickScript.append(inquiryUrl);
            onClickScript.append("\", \"");
            onClickScript.append(paramMapString);
            onClickScript.append("\", ");
            onClickScript.append(lightBoxShow);
            onClickScript.append(", ");
            onClickScript.append(lightBoxOptions);
            onClickScript.append(");");

            directInquiryAction.setPerformDirtyValidation(false);
            directInquiryAction.setActionScript(onClickScript.toString());

            setRender(true);
        }
    }

    /**
     * Gets text to prepend to the inquiry link title
     *
     * @param dataObjectClass data object class being inquired into
     * @return inquiry link title
     */
    public String createTitleText(Class<?> dataObjectClass, Map<String, String> inquiryKeyValues) {
        // use manually configured title if exists
        if (StringUtils.isNotBlank(getTitle())) {
            return getTitle();
        }

        String titleText = "";

        String titlePrefix = CoreApiServiceLocator.getKualiConfigurationService()
                .getPropertyValueAsString(INQUIRY_TITLE_PREFIX);
        if (StringUtils.isNotBlank(titlePrefix)) {
            titleText += titlePrefix + " ";
        }

        String objectLabel = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary()
                .getDataObjectEntry(dataObjectClass.getName()).getObjectLabel();
        if (StringUtils.isNotBlank(objectLabel)) {
            titleText += objectLabel + " ";
        }

        if (StringUtils.isNotBlank(titleText)) {
            String titlePostfix = CoreApiServiceLocator.getKualiConfigurationService()
                    .getPropertyValueAsString(INQUIRY_TITLE_POSTFIX);
            if (StringUtils.isNotBlank(titlePostfix)) {
                titleText += titlePostfix + " ";
            }
        }

        return KRADUtils.buildAttributeTitleString(titleText, dataObjectClass, inquiryKeyValues);
    }

    /**
     * Returns the URL for the inquiry for which parameters will be added
     *
     * <p>
     * The base URL includes the domain, context, and controller mapping for the inquiry invocation. Parameters are
     * then added based on configuration to complete the URL. This is generally defaulted to the application URL and
     * internal KRAD servlet mapping, but can be changed to invoke another application such as the Rice standalone
     * server
     * </p>
     *
     * @return inquiry base URL
     */
    @BeanTagAttribute
    public String getBaseInquiryUrl() {
        return this.baseInquiryUrl;
    }

    /**
     * Setter for the inquiry base url (domain, context, and controller)
     *
     * @param baseInquiryUrl
     */
    public void setBaseInquiryUrl(String baseInquiryUrl) {
        this.baseInquiryUrl = baseInquiryUrl;
    }

    /**
     * Full class name the inquiry should be provided for
     *
     * <p>
     * This is passed on to the inquiry request for the data object the lookup should be rendered for. This is then
     * used by the inquiry framework to select the lookup view (if more than one inquiry view exists for the same
     * data object class name, the {@link #getViewName()} property should be specified to select the view to render).
     * </p>
     *
     * @return inquiry class name
     */
    @BeanTagAttribute
    public String getDataObjectClassName() {
        return this.dataObjectClassName;
    }

    /**
     * Setter for the class name that inquiry should be provided for
     *
     * @param dataObjectClassName
     */
    public void setDataObjectClassName(String dataObjectClassName) {
        this.dataObjectClassName = dataObjectClassName;
    }

    /**
     * When multiple target inquiry views exists for the same data object class, the view name can be set to
     * determine which one to use
     *
     * <p>
     * When creating multiple inquiry views for the same data object class, the view name can be specified for the
     * different versions (for example 'simple' and 'advanced'). When multiple inquiry views exist the view name must
     * be sent with the data object class for the request. Note the view id can be alternatively used to uniquely
     * identify the inquiry view
     * </p>
     * @return view name
     */
    @BeanTagAttribute
    public String getViewName() {
        return this.viewName;
    }

    /**
     * Setter for the view name configured on the inquiry view that should be invoked by the inquiry widget
     *
     * @param viewName
     */
    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    /**
     * Map that determines what properties from a calling view will be sent to properties on the inquiry data object
     *
     * <p>
     * When invoking an inquiry view, a query is done against the inquiries configured data object and the resulting
     * record is display. The values for the properties configured within the inquiry parameters Map will be
     * pulled and passed along as values for the inquiry data object properties (thus they form the criteria for
     * the inquiry)
     * </p>
     *
     * @return mapping of calling view properties to inquiry data object properties
     */
    @BeanTagAttribute
    public Map<String, String> getInquiryParameters() {
        return this.inquiryParameters;
    }

    /**
     * Setter for the map that determines what property values on the calling view will be sent to properties on the
     * inquiry data object
     *
     * @param inquiryParameters
     */
    public void setInquiryParameters(Map<String, String> inquiryParameters) {
        this.inquiryParameters = inquiryParameters;
    }

    /**
     * {@code Link} that will be rendered for an inquiry
     *
     * @return the inquiry link
     */
    @BeanTagAttribute
    public Link getInquiryLink() {
        return this.inquiryLink;
    }

    /**
     * Setter for the inquiry {@code Link}
     *
     * @param inquiryLink the inquiry {@link Link} object
     */
    public void setInquiryLink(Link inquiryLink) {
        this.inquiryLink = inquiryLink;
    }

    /**
     * {@code Action} that will be rendered next to the field for a direct inquiry
     *
     * @return the directInquiryAction
     */
    @BeanTagAttribute
    public Action getDirectInquiryAction() {
        return this.directInquiryAction;
    }

    /**
     * Setter for the direct inquiry {@code Action}
     *
     * @param directInquiryAction the direct inquiry {@link Action}
     */
    public void setDirectInquiryAction(Action directInquiryAction) {
        this.directInquiryAction = directInquiryAction;
    }

    /**
     * Indicates that the direct inquiry will not be rendered
     *
     * @return true if the direct inquiry should be rendered, false if not
     */
    @BeanTagAttribute
    public boolean isEnableDirectInquiry() {
        return enableDirectInquiry;
    }

    /**
     * Setter for the hideDirectInquiry flag
     *
     * @param enableDirectInquiry
     */
    public void setEnableDirectInquiry(boolean enableDirectInquiry) {
        this.enableDirectInquiry = enableDirectInquiry;
    }

    /**
     * Determines whether a normal or direct inquiry should be enabled
     *
     * @return true if parent component is read only, false otherwise
     */
    protected boolean isParentReadOnly() {
        return parentReadOnly;
    }

    /**
     * Determines whether a normal or direct inquiry should be enabled
     *
     * <p>
     * Used by unit tests and internally
     * </p>
     *
     * @param parentReadOnly true if parent component is read only, false otherwise
     */
    protected void setParentReadOnly(boolean parentReadOnly) {
        this.parentReadOnly = parentReadOnly;
    }

    /**
     * Determines whether inquiry parameters adjusted
     *
     * @return true if adjusted
     */
    public boolean isAdjustInquiryParameters() {
        return adjustInquiryParameters;
    }

    /**
     * Determines whether inquiry parameters adjusted
     *
     * <p>
     * Used internally
     * </p>
     *
     * @param adjustInquiryParameters
     */
    protected void setAdjustInquiryParameters(boolean adjustInquiryParameters) {
        this.adjustInquiryParameters = adjustInquiryParameters;
    }

    /**
     * Sets the field binding information
     *
     * <p>
     * Sets the field binding information
     * </p>
     *
     * @param fieldBindingInfo
     */
    protected void setFieldBindingInfo(BindingInfo fieldBindingInfo) {
        this.fieldBindingInfo = fieldBindingInfo;
    }
}