View Javadoc
1   /**
2    * Copyright 2005-2016 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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.uif.RemotableAttributeField;
20  import org.kuali.rice.krad.uif.component.BindingInfo;
21  import org.kuali.rice.krad.uif.component.ComponentBase;
22  import org.kuali.rice.krad.uif.component.DataBinding;
23  import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
24  import org.kuali.rice.krad.uif.container.Container;
25  import org.kuali.rice.krad.uif.util.CloneUtils;
26  import org.kuali.rice.krad.uif.util.ComponentFactory;
27  import org.kuali.rice.krad.uif.view.View;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  
32  /**
33   * A placeholder in the configuration for a <code>Container</code> list of items that will be invoked to
34   * retrieve a list of {@link RemotableAttributeField} instances which will then be inserted into the containers
35   * list at the position of the holder
36   *
37   * <p>
38   * Since remotable fields are dynamic by nature, the individual fields cannot be configured initially with the
39   * container. Further more the properties for the field are constructed with code. This gives the ability to specify
40   * where that list of fields should be placed, along with configured on how to retrieve the remote fields.
41   * </p>
42   *
43   * <p>
44   * The fetching properties are used to configure what method to invoke that will return the list of remotable fields.
45   * Specifying the {@link #getFetchingMethodToCall()} only assumes the method is on the view helper service for the
46   * contained view. For invoking other classes, such as services or static classes, use {@link
47   * #getFetchingMethodInvoker()}
48   * </p>
49   *
50   * <p>
51   * The list of remotable fields should bind to a Map property on the model. The {@link #getPropertyName()} and
52   * {@link #getBindingInfo()} properties specify the path to this property. The property names configured on the
53   * returned fields are assumed to be keys in that above configured map, with the corresponding map value giving the
54   * actual model value for the remote field.
55   * </p>
56   *
57   * <p>
58   * e.g. configuration
59   * {@code
60   *    <property name="items">
61        <list>
62          <bean parent="RemoteFieldsHolder" p:propertyName="remoteFieldValuesMap"
63                p:fetchingMethodToCall="retrieveRemoteFields"/>
64   *    ...
65   * }
66   *
67   * This example will invoke a method named 'retrieveRemoteFields' on the view helper service, which should return
68   * a list of {@link RemotableAttributeField} instances. The view, model instance, and parent container will be sent
69   * to the method as arguments.
70   *
71   * The returned fields will be translated to {@link InputField} instances that bind to a map property named
72   * 'remoteFieldValuesMap' on the model.
73   * </p>
74   *
75   * @author Kuali Rice Team (rice.collab@kuali.org)
76   */
77  public class RemoteFieldsHolder extends ComponentBase implements DataBinding {
78      private static final long serialVersionUID = -8493923312021633727L;
79      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RemoteFieldsHolder.class);
80  
81      private String propertyName;
82      private BindingInfo bindingInfo;
83  
84      private String fetchingMethodToCall;
85      private MethodInvokerConfig fetchingMethodInvoker;
86  
87      public RemoteFieldsHolder() {
88          super();
89      }
90  
91      /**
92       * Invokes the configured fetching method to retrieve a list of remotable fields, then invoked the
93       * {@code ComponentFactory} to translate the fields, and finally sets up the binding for the attribute fields
94       *
95       * @param view - view instance the container belongs to, sent to the fetching method
96       * @param model - object containing the view data, sent to the fetching method
97       * @param parent - container instance that holder is configured for, sent to the fetching method
98       * @return List<AttributeField> list of attribute fields that should be placed into container, if no remotable
99       * fields were returned from the fetching method the list will be empty
100      */
101     public List<InputField> fetchAndTranslateRemoteFields(View view, Object model, Container parent) {
102         if (StringUtils.isBlank(fetchingMethodToCall) && (fetchingMethodInvoker == null)) {
103             throw new RuntimeException("");
104         }
105 
106         if (fetchingMethodInvoker == null) {
107             fetchingMethodInvoker = new MethodInvokerConfig();
108         }
109 
110         // if method not set on invoker, use fetchingMethodToCall, note staticMethod could be set(don't know since
111         // there is not a getter), if so it will override the target method in prepare
112         if (StringUtils.isBlank(fetchingMethodInvoker.getTargetMethod())) {
113             fetchingMethodInvoker.setTargetMethod(fetchingMethodToCall);
114         }
115 
116         // if target class or object not set, use view helper service
117         if ((fetchingMethodInvoker.getTargetClass() == null) && (fetchingMethodInvoker.getTargetObject() == null)) {
118             fetchingMethodInvoker.setTargetObject(view.getViewHelperService());
119         }
120 
121         Object[] arguments = new Object[3];
122         arguments[0] = view;
123         arguments[1] = model;
124         arguments[2] = parent;
125         fetchingMethodInvoker.setArguments(arguments);
126 
127         // invoke method
128         List<RemotableAttributeField> remotableFields = null;
129         try {
130             LOG.debug("Invoking fetching method: " + fetchingMethodInvoker.getTargetMethod());
131             fetchingMethodInvoker.prepare();
132 
133             remotableFields = (List<RemotableAttributeField>) fetchingMethodInvoker.invoke();
134 
135         } catch (Exception e) {
136             LOG.error("Error invoking fetching method", e);
137             throw new RuntimeException("Error invoking fetching method", e);
138         }
139 
140         // do translation
141         List<InputField> attributeFields = new ArrayList<InputField>();
142         if ((remotableFields != null) && !remotableFields.isEmpty()) {
143             attributeFields = ComponentFactory.translateRemotableFields(remotableFields);
144         }
145 
146         // set binding info on the translated fields
147         if (bindingInfo == null) {
148             bindingInfo = new BindingInfo();
149         }
150 
151         // property name should point to a Map that holds attribute name/value pairs
152         bindingInfo.addToBindByNamePrefix(propertyName);
153         bindingInfo.setBindToMap(true);
154 
155         for (InputField field : attributeFields) {
156             BindingInfo fieldBindingInfo = CloneUtils.deepClone(bindingInfo);
157             fieldBindingInfo.setDefaults(view, field.getPropertyName());
158             field.setBindingInfo(fieldBindingInfo);
159 
160             view.assignComponentIds(field);
161         }
162 
163         return attributeFields;
164     }
165 
166     @Override
167     public String getComponentTypeName() {
168         return "RemoteFieldsHolder";
169     }
170 
171     /**
172      * Path to the Map property that the translated fields bind to
173      *
174      * <p>
175      * It is assumed this property points to a Map where the property names on the returned remotable fields
176      * are keys in that map, with the corresponding map value giving the model value for the field
177      * </p>
178      *
179      * @return String path to property on model
180      */
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     public BindingInfo getBindingInfo() {
205         return bindingInfo;
206     }
207 
208     /**
209      * Setter for the Map property binding info instance
210      *
211      * @param bindingInfo
212      */
213     public void setBindingInfo(BindingInfo bindingInfo) {
214         this.bindingInfo = bindingInfo;
215     }
216 
217     /**
218      * Name of the method to invoke for retrieving the list of remotable fields
219      *
220      * <p>
221      * When only the fetching method to call is configured it is assumed to be a valid method on the view
222      * helper service for the containing view. The method name must accept the view, model object, and parent
223      * container as arguments, and return a list of {@link RemotableAttributeField} instances.
224      * </p>
225      *
226      * <p>
227      * For invoking the method on classes other than the view helper service, see {@link #getFetchingMethodInvoker()}
228      * </p>
229      *
230      * @return String name of method to invoke for fetching remote fields
231      */
232     public String getFetchingMethodToCall() {
233         return fetchingMethodToCall;
234     }
235 
236     /**
237      * Setter for the fetching method to call
238      *
239      * @param fetchingMethodToCall
240      */
241     public void setFetchingMethodToCall(String fetchingMethodToCall) {
242         this.fetchingMethodToCall = fetchingMethodToCall;
243     }
244 
245     /**
246      * Configuration for the method to invoke for retrieving the list of remotable fields
247      *
248      * <p>
249      * Through the method invoker config, a service or static class can be configured along with the
250      * method name that will be invoked. The method name must accept the view, model object, and parent
251      * container as arguments, and return a list of {@link RemotableAttributeField} instances.
252      * </p>
253      *
254      * <p>
255      * Note the {@link org.kuali.rice.krad.uif.component.MethodInvokerConfig#getTargetMethod()} property can
256      * be configured, or the {@link #getFetchingMethodToCall()}. In the case of both configurations, the target
257      * method on the method invoker config will be used
258      * </p>
259      *
260      * @return MethodInvokerConfig instance containing method configuration
261      */
262     public MethodInvokerConfig getFetchingMethodInvoker() {
263         return fetchingMethodInvoker;
264     }
265 
266     /**
267      * Setter for the fetching method to invoke configuration
268      *
269      * @param fetchingMethodInvoker
270      */
271     public void setFetchingMethodInvoker(MethodInvokerConfig fetchingMethodInvoker) {
272         this.fetchingMethodInvoker = fetchingMethodInvoker;
273     }
274 }