View Javadoc
1   /**
2    * Copyright 2005-2014 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.krad.uif.field;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  
21  import org.apache.commons.lang.StringUtils;
22  import org.kuali.rice.core.api.uif.RemotableAttributeField;
23  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
24  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
25  import org.kuali.rice.krad.uif.component.BindingInfo;
26  import org.kuali.rice.krad.uif.component.ComponentBase;
27  import org.kuali.rice.krad.uif.component.DataBinding;
28  import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
29  import org.kuali.rice.krad.uif.container.Container;
30  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
31  import org.kuali.rice.krad.uif.util.CloneUtils;
32  import org.kuali.rice.krad.uif.util.ComponentFactory;
33  
34  /**
35   * A placeholder in the configuration for a <code>Container</code> list of items that will be invoked to
36   * retrieve a list of {@link RemotableAttributeField} instances which will then be inserted into the containers
37   * list at the position of the holder
38   *
39   * <p>
40   * Since remotable fields are dynamic by nature, the individual fields cannot be configured initially with the
41   * container. Further more the properties for the field are constructed with code. This gives the ability to specify
42   * where that list of fields should be placed, along with configured on how to retrieve the remote fields.
43   * </p>
44   *
45   * <p>
46   * The fetching properties are used to configure what method to invoke that will return the list of remotable fields.
47   * Specifying the {@link #getFetchingMethodToCall()} only assumes the method is on the view helper service for the
48   * contained view. For invoking other classes, such as services or static classes, use {@link
49   * #getFetchingMethodInvoker()}
50   * </p>
51   *
52   * <p>
53   * The list of remotable fields should bind to a Map property on the model. The {@link #getPropertyName()} and
54   * {@link #getBindingInfo()} properties specify the path to this property. The property names configured on the
55   * returned fields are assumed to be keys in that above configured map, with the corresponding map value giving the
56   * actual model value for the remote field.
57   * </p>
58   *
59   * <p>
60   * e.g. configuration
61   * {@code
62   * <property name="items">
63   * <list>
64   * <bean parent="RemoteFieldsHolder" p:propertyName="remoteFieldValuesMap"
65   * p:fetchingMethodToCall="retrieveRemoteFields"/>
66   * ...
67   * }
68   *
69   * This example will invoke a method named 'retrieveRemoteFields' on the view helper service, which should return
70   * a list of {@link RemotableAttributeField} instances. The view, model instance, and parent container will be sent
71   * to the method as arguments.
72   *
73   * The returned fields will be translated to {@link InputField} instances that bind to a map property named
74   * 'remoteFieldValuesMap' on the model.
75   * </p>
76   *
77   * @author Kuali Rice Team (rice.collab@kuali.org)
78   */
79  @BeanTag(name = "remotableFieldsPlaceholderConfig-bean", parent = "Uif-RemotableFieldsPlaceholderConfig")
80  public class RemoteFieldsHolder extends ComponentBase implements DataBinding {
81      private static final long serialVersionUID = -8493923312021633727L;
82      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RemoteFieldsHolder.class);
83  
84      private String propertyName;
85      private BindingInfo bindingInfo;
86  
87      private String fetchingMethodToCall;
88      private MethodInvokerConfig fetchingMethodInvoker;
89  
90      public RemoteFieldsHolder() {
91          super();
92      }
93  
94      /**
95       * Invokes the configured fetching method to retrieve a list of remotable fields, then invoked the
96       * {@code ComponentFactory} to translate the fields, and finally sets up the binding for the attribute fields
97       *
98       * @param parent container instance that holder is configured for, sent to the fetching method
99       * @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 = CloneUtils.deepClone(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 }