001/**
002 * Copyright 2005-2016 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.web.bind;
017
018import org.kuali.rice.core.web.format.Formatter;
019import org.kuali.rice.krad.uif.field.DataField;
020import org.kuali.rice.krad.uif.view.ViewIndex;
021import org.kuali.rice.krad.uif.view.ViewModel;
022import org.springframework.beans.BeanWrapperImpl;
023import org.springframework.beans.BeansException;
024import org.springframework.beans.InvalidPropertyException;
025import org.springframework.beans.PropertyValue;
026
027import java.beans.PropertyDescriptor;
028import java.beans.PropertyEditor;
029import java.util.HashSet;
030import java.util.Set;
031
032/**
033 * Class is a top level BeanWrapper for a UIF View Model
034 *
035 * <p>
036 * Registers custom property editors configured on the field associated with the property name for which
037 * we are getting or setting a value. In addition determines if the field requires encryption and if so applies
038 * the {@link UifEncryptionPropertyEditorWrapper}
039 * </p>
040 *
041 * @author Kuali Rice Team (rice.collab@kuali.org)
042 */
043public class UifViewBeanWrapper extends BeanWrapperImpl {
044    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifViewBeanWrapper.class);
045
046    // this is a handle to the target object so we don't have to cast so often
047    private ViewModel model;
048
049    // this stores all properties this wrapper has already checked
050    // with the view so the service isn't called again
051    private Set<String> processedProperties;
052
053    public UifViewBeanWrapper(ViewModel model) {
054        super(model);
055
056        this.model = model;
057        this.processedProperties = new HashSet<String>();
058    }
059
060    /**
061     * Attempts to find a corresponding data field for the given property name in the current view or previous view,
062     * then if the field has a property editor configured it is registered with the property editor registry to use
063     * for this property
064     *
065     * @param propertyName - name of the property to find field and editor for
066     */
067    protected void registerEditorFromView(String propertyName) {
068        if (LOG.isDebugEnabled()) {
069            LOG.debug("Attempting to find property editor for property '" + propertyName + "'");
070        }
071
072        // check if we already processed this property for this BeanWrapper instance
073        if (processedProperties.contains(propertyName)) {
074            return;
075        }
076
077        // when rendering the page, we will use the view that was just built, for post
078        // we need to use the posted view (not the newly initialized view)
079        ViewIndex viewIndex = null;
080        if (model.getView() != null) {
081            viewIndex = model.getView().getViewIndex();
082        } else if (model.getPostedView() != null) {
083            viewIndex = model.getPostedView().getViewIndex();
084        }
085
086        // if view index instance not established we cannot determine property editors
087        if (viewIndex == null) {
088            return;
089        }
090
091        PropertyEditor propertyEditor = null;
092        boolean requiresEncryption = false;
093
094        if (viewIndex.getFieldPropertyEditors().containsKey(propertyName)) {
095            propertyEditor = viewIndex.getFieldPropertyEditors().get(propertyName);
096        } else if (viewIndex.getSecureFieldPropertyEditors().containsKey(propertyName)) {
097            propertyEditor = viewIndex.getSecureFieldPropertyEditors().get(propertyName);
098            requiresEncryption = true;
099        }
100
101        if (propertyEditor != null) {
102            if (LOG.isDebugEnabled()) {
103                LOG.debug("Registering custom editor for property path '" + propertyName
104                        + "' and property editor class '" + propertyEditor.getClass().getName() + "'");
105            }
106
107            if (requiresEncryption) {
108                if (LOG.isDebugEnabled()) {
109                    LOG.debug("Enabling encryption for custom editor '" + propertyName +
110                            "' and property editor class '" + propertyEditor.getClass().getName() + "'");
111                }
112                this.registerCustomEditor(null, propertyName, new UifEncryptionPropertyEditorWrapper(propertyEditor));
113            } else {
114                this.registerCustomEditor(null, propertyName, propertyEditor);
115            }
116        } else if (requiresEncryption) {
117            if (LOG.isDebugEnabled()) {
118                LOG.debug("No custom formatter for property path '" + propertyName
119                        + "' but property does require encryption");
120            }
121
122            this.registerCustomEditor(null, propertyName, new UifEncryptionPropertyEditorWrapper(
123                    findEditorForPropertyName(propertyName)));
124        }
125
126        processedProperties.add(propertyName);
127    }
128
129    protected PropertyEditor findEditorForPropertyName(String propertyName) {
130        Class<?> clazz = getPropertyType(propertyName);
131        if (LOG.isDebugEnabled()) {
132            LOG.debug("Attempting retrieval of property editor using class '"
133                    + clazz
134                    + "' and property path '"
135                    + propertyName
136                    + "'");
137        }
138
139        PropertyEditor editor = findCustomEditor(clazz, propertyName);
140        if (editor == null) {
141            if (LOG.isDebugEnabled()) {
142                LOG.debug("No custom property editor found using class '"
143                        + clazz
144                        + "' and property path '"
145                        + propertyName
146                        + "'. Attempting to find default property editor class.");
147            }
148            editor = getDefaultEditor(clazz);
149        }
150
151        return editor;
152    }
153
154    @Override
155    public Class<?> getPropertyType(String propertyName) throws BeansException {
156        try {
157            PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
158            if (pd != null) {
159                return pd.getPropertyType();
160            }
161
162            // Maybe an indexed/mapped property...
163            Object value = super.getPropertyValue(propertyName);
164            if (value != null) {
165                return value.getClass();
166            }
167
168            // Check to see if there is a custom editor,
169            // which might give an indication on the desired target type.
170            Class<?> editorType = guessPropertyTypeFromEditors(propertyName);
171            if (editorType != null) {
172                return editorType;
173            }
174        } catch (InvalidPropertyException ex) {
175            // Consider as not determinable.
176        }
177
178        return null;
179    }
180
181    @Override
182    public Object getPropertyValue(String propertyName) throws BeansException {
183        registerEditorFromView(propertyName);
184        return super.getPropertyValue(propertyName);
185    }
186
187    @Override
188    public void setPropertyValue(PropertyValue pv) throws BeansException {
189        registerEditorFromView(pv.getName());
190        super.setPropertyValue(pv);
191    }
192
193    @Override
194    public void setPropertyValue(String propertyName, Object value) throws BeansException {
195        registerEditorFromView(propertyName);
196        super.setPropertyValue(propertyName, value);
197    }
198
199    @Override
200    public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
201        //TODO clear cache?
202        model = (ViewModel) object;
203        super.setWrappedInstance(object, nestedPath, rootObject);
204    }
205
206    @Override
207    public void setWrappedInstance(Object object) {
208        //TODO clear cache?
209        model = (ViewModel) object;
210        super.setWrappedInstance(object);
211    }
212}