001    /**
002     * Copyright 2005-2014 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     */
016    package org.kuali.rice.krad.uif.field;
017    
018    import java.util.ArrayList;
019    import java.util.List;
020    
021    import org.apache.commons.lang.StringUtils;
022    import org.kuali.rice.core.api.uif.RemotableAttributeField;
023    import org.kuali.rice.krad.datadictionary.parse.BeanTag;
024    import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
025    import org.kuali.rice.krad.uif.component.BindingInfo;
026    import org.kuali.rice.krad.uif.component.ComponentBase;
027    import org.kuali.rice.krad.uif.component.DataBinding;
028    import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
029    import org.kuali.rice.krad.uif.container.Container;
030    import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
031    import org.kuali.rice.krad.uif.util.ComponentFactory;
032    import org.kuali.rice.krad.uif.util.CopyUtils;
033    
034    /**
035     * A placeholder in the configuration for a <code>Container</code> list of items that will be invoked to
036     * retrieve a list of {@link RemotableAttributeField} instances which will then be inserted into the containers
037     * list at the position of the holder
038     *
039     * <p>
040     * Since remotable fields are dynamic by nature, the individual fields cannot be configured initially with the
041     * container. Further more the properties for the field are constructed with code. This gives the ability to specify
042     * where that list of fields should be placed, along with configured on how to retrieve the remote fields.
043     * </p>
044     *
045     * <p>
046     * The fetching properties are used to configure what method to invoke that will return the list of remotable fields.
047     * Specifying the {@link #getFetchingMethodToCall()} only assumes the method is on the view helper service for the
048     * contained view. For invoking other classes, such as services or static classes, use {@link
049     * #getFetchingMethodInvoker()}
050     * </p>
051     *
052     * <p>
053     * The list of remotable fields should bind to a Map property on the model. The {@link #getPropertyName()} and
054     * {@link #getBindingInfo()} properties specify the path to this property. The property names configured on the
055     * returned fields are assumed to be keys in that above configured map, with the corresponding map value giving the
056     * actual model value for the remote field.
057     * </p>
058     *
059     * <p>
060     * e.g. configuration
061     * {@code
062     * <property name="items">
063     * <list>
064     * <bean parent="RemoteFieldsHolder" p:propertyName="remoteFieldValuesMap"
065     * p:fetchingMethodToCall="retrieveRemoteFields"/>
066     * ...
067     * }
068     *
069     * This example will invoke a method named 'retrieveRemoteFields' on the view helper service, which should return
070     * a list of {@link RemotableAttributeField} instances. The view, model instance, and parent container will be sent
071     * to the method as arguments.
072     *
073     * The returned fields will be translated to {@link InputField} instances that bind to a map property named
074     * 'remoteFieldValuesMap' on the model.
075     * </p>
076     *
077     * @author Kuali Rice Team (rice.collab@kuali.org)
078     */
079    @BeanTag(name = "remotableFieldsPlaceholderConfig-bean", parent = "Uif-RemotableFieldsPlaceholderConfig")
080    public class RemoteFieldsHolder extends ComponentBase implements DataBinding {
081        private static final long serialVersionUID = -8493923312021633727L;
082        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RemoteFieldsHolder.class);
083    
084        private String propertyName;
085        private BindingInfo bindingInfo;
086    
087        private String fetchingMethodToCall;
088        private MethodInvokerConfig fetchingMethodInvoker;
089    
090        public RemoteFieldsHolder() {
091            super();
092        }
093    
094        /**
095         * Invokes the configured fetching method to retrieve a list of remotable fields, then invoked the
096         * {@code ComponentFactory} to translate the fields, and finally sets up the binding for the attribute fields
097         *
098         * @param parent container instance that holder is configured for, sent to the fetching method
099         * @return list of attribute fields that should be placed into container, if no remotable
100         *         fields were returned from the fetching method the list will be empty
101         */
102        public List<InputField> fetchAndTranslateRemoteFields(Container parent) {
103            if (StringUtils.isBlank(fetchingMethodToCall) && (fetchingMethodInvoker == null)) {
104                throw new RuntimeException("");
105            }
106    
107            if (fetchingMethodInvoker == null) {
108                fetchingMethodInvoker = new MethodInvokerConfig();
109            }
110    
111            // if method not set on invoker, use fetchingMethodToCall, note staticMethod could be set(don't know since
112            // there is not a getter), if so it will override the target method in prepare
113            if (StringUtils.isBlank(fetchingMethodInvoker.getTargetMethod())) {
114                fetchingMethodInvoker.setTargetMethod(fetchingMethodToCall);
115            }
116    
117            // if target class or object not set, use view helper service
118            if ((fetchingMethodInvoker.getTargetClass() == null) && (fetchingMethodInvoker.getTargetObject() == null)) {
119                fetchingMethodInvoker.setTargetObject(ViewLifecycle.getHelper());
120            }
121    
122            Object[] arguments = new Object[3];
123            arguments[0] = ViewLifecycle.getView();
124            arguments[1] = ViewLifecycle.getModel();
125            arguments[2] = parent;
126            fetchingMethodInvoker.setArguments(arguments);
127    
128            // invoke method
129            List<RemotableAttributeField> remotableFields = null;
130            try {
131                LOG.debug("Invoking fetching method: " + fetchingMethodInvoker.getTargetMethod());
132                fetchingMethodInvoker.prepare();
133    
134                remotableFields = (List<RemotableAttributeField>) fetchingMethodInvoker.invoke();
135    
136            } catch (Exception e) {
137                LOG.error("Error invoking fetching method", e);
138                throw new RuntimeException("Error invoking fetching method", e);
139            }
140    
141            // do translation
142            List<InputField> attributeFields = new ArrayList<InputField>();
143            if ((remotableFields != null) && !remotableFields.isEmpty()) {
144                attributeFields = ComponentFactory.translateRemotableFields(remotableFields);
145            }
146    
147            // set binding info on the translated fields
148            if (bindingInfo == null) {
149                bindingInfo = new BindingInfo();
150            }
151    
152            // property name should point to a Map that holds attribute name/value pairs
153            bindingInfo.addToBindByNamePrefix(propertyName);
154            bindingInfo.setBindToMap(true);
155    
156            for (InputField field : attributeFields) {
157                BindingInfo fieldBindingInfo = CopyUtils.copy(bindingInfo);
158                fieldBindingInfo.setDefaults(ViewLifecycle.getView(), field.getPropertyName());
159                field.setBindingInfo(fieldBindingInfo);
160            }
161    
162            return attributeFields;
163        }
164    
165        @Override
166        public String getComponentTypeName() {
167            return "RemoteFieldsHolder";
168        }
169    
170        /**
171         * Path to the Map property that the translated fields bind to
172         *
173         * <p>
174         * It is assumed this property points to a Map where the property names on the returned remotable fields
175         * are keys in that map, with the corresponding map value giving the model value for the field
176         * </p>
177         *
178         * @return path to property on model
179         */
180        @BeanTagAttribute(name = "propertyName")
181        public String getPropertyName() {
182            return propertyName;
183        }
184    
185        /**
186         * Setter for the property name that points to the binding Map
187         *
188         * @param propertyName
189         */
190        public void setPropertyName(String propertyName) {
191            this.propertyName = propertyName;
192        }
193    
194        /**
195         * Can be used to for more complex binding paths
196         *
197         * <p>
198         * Generally not necessary to set on a field level, any default object path or binding prefixes set
199         * on the view or container will be inherited
200         * </p>
201         *
202         * @return BindingInfo instance containing binding information for the Map property
203         */
204        @BeanTagAttribute(name = "bindingInfo", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
205        public BindingInfo getBindingInfo() {
206            return bindingInfo;
207        }
208    
209        /**
210         * Setter for the Map property binding info instance
211         *
212         * @param bindingInfo
213         */
214        public void setBindingInfo(BindingInfo bindingInfo) {
215            this.bindingInfo = bindingInfo;
216        }
217    
218        /**
219         * Name of the method to invoke for retrieving the list of remotable fields
220         *
221         * <p>
222         * When only the fetching method to call is configured it is assumed to be a valid method on the view
223         * helper service for the containing view. The method name must accept the view, model object, and parent
224         * container as arguments, and return a list of {@link RemotableAttributeField} instances.
225         * </p>
226         *
227         * <p>
228         * For invoking the method on classes other than the view helper service, see {@link #getFetchingMethodInvoker()}
229         * </p>
230         *
231         * @return name of method to invoke for fetching remote fields
232         */
233        @BeanTagAttribute(name = "fetchingMethodToCall")
234        public String getFetchingMethodToCall() {
235            return fetchingMethodToCall;
236        }
237    
238        /**
239         * Setter for the fetching method to call
240         *
241         * @param fetchingMethodToCall
242         */
243        public void setFetchingMethodToCall(String fetchingMethodToCall) {
244            this.fetchingMethodToCall = fetchingMethodToCall;
245        }
246    
247        /**
248         * Configuration for the method to invoke for retrieving the list of remotable fields
249         *
250         * <p>
251         * Through the method invoker config, a service or static class can be configured along with the
252         * method name that will be invoked. The method name must accept the view, model object, and parent
253         * container as arguments, and return a list of {@link RemotableAttributeField} instances.
254         * </p>
255         *
256         * <p>
257         * Note the {@link org.kuali.rice.krad.uif.component.MethodInvokerConfig#getTargetMethod()} property can
258         * be configured, or the {@link #getFetchingMethodToCall()}. In the case of both configurations, the target
259         * method on the method invoker config will be used
260         * </p>
261         *
262         * @return MethodInvokerConfig instance containing method configuration
263         */
264        @BeanTagAttribute(name = "fetchingMethodInvoker", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
265        public MethodInvokerConfig getFetchingMethodInvoker() {
266            return fetchingMethodInvoker;
267        }
268    
269        /**
270         * Setter for the fetching method to invoke configuration
271         *
272         * @param fetchingMethodInvoker
273         */
274        public void setFetchingMethodInvoker(MethodInvokerConfig fetchingMethodInvoker) {
275            this.fetchingMethodInvoker = fetchingMethodInvoker;
276        }
277    
278    }