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.datadictionary.parse.BeanTag;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
22  import org.kuali.rice.krad.uif.component.BindingInfo;
23  import org.kuali.rice.krad.uif.component.ComponentBase;
24  import org.kuali.rice.krad.uif.component.DataBinding;
25  import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
26  import org.kuali.rice.krad.uif.container.Container;
27  import org.kuali.rice.krad.uif.util.CloneUtils;
28  import org.kuali.rice.krad.uif.util.ComponentFactory;
29  import org.kuali.rice.krad.uif.view.View;
30  
31  import java.util.ArrayList;
32  import java.util.List;
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 view view instance the container belongs to, sent to the fetching method
99       * @param model object containing the view data, sent to the fetching method
100      * @param parent container instance that holder is configured for, sent to the fetching method
101      * @return list of attribute fields that should be placed into container, if no remotable
102      *         fields were returned from the fetching method the list will be empty
103      */
104     public List<InputField> fetchAndTranslateRemoteFields(View view, Object model, Container parent) {
105         if (StringUtils.isBlank(fetchingMethodToCall) && (fetchingMethodInvoker == null)) {
106             throw new RuntimeException("");
107         }
108 
109         if (fetchingMethodInvoker == null) {
110             fetchingMethodInvoker = new MethodInvokerConfig();
111         }
112 
113         // if method not set on invoker, use fetchingMethodToCall, note staticMethod could be set(don't know since
114         // there is not a getter), if so it will override the target method in prepare
115         if (StringUtils.isBlank(fetchingMethodInvoker.getTargetMethod())) {
116             fetchingMethodInvoker.setTargetMethod(fetchingMethodToCall);
117         }
118 
119         // if target class or object not set, use view helper service
120         if ((fetchingMethodInvoker.getTargetClass() == null) && (fetchingMethodInvoker.getTargetObject() == null)) {
121             fetchingMethodInvoker.setTargetObject(view.getViewHelperService());
122         }
123 
124         Object[] arguments = new Object[3];
125         arguments[0] = view;
126         arguments[1] = model;
127         arguments[2] = parent;
128         fetchingMethodInvoker.setArguments(arguments);
129 
130         // invoke method
131         List<RemotableAttributeField> remotableFields = null;
132         try {
133             LOG.debug("Invoking fetching method: " + fetchingMethodInvoker.getTargetMethod());
134             fetchingMethodInvoker.prepare();
135 
136             remotableFields = (List<RemotableAttributeField>) fetchingMethodInvoker.invoke();
137 
138         } catch (Exception e) {
139             LOG.error("Error invoking fetching method", e);
140             throw new RuntimeException("Error invoking fetching method", e);
141         }
142 
143         // do translation
144         List<InputField> attributeFields = new ArrayList<InputField>();
145         if ((remotableFields != null) && !remotableFields.isEmpty()) {
146             attributeFields = ComponentFactory.translateRemotableFields(remotableFields);
147         }
148 
149         // set binding info on the translated fields
150         if (bindingInfo == null) {
151             bindingInfo = new BindingInfo();
152         }
153 
154         // property name should point to a Map that holds attribute name/value pairs
155         bindingInfo.addToBindByNamePrefix(propertyName);
156         bindingInfo.setBindToMap(true);
157 
158         for (InputField field : attributeFields) {
159             BindingInfo fieldBindingInfo = CloneUtils.deepClone(bindingInfo);
160             fieldBindingInfo.setDefaults(view, field.getPropertyName());
161             field.setBindingInfo(fieldBindingInfo);
162 
163             view.assignComponentIds(field);
164         }
165 
166         return attributeFields;
167     }
168 
169     @Override
170     public String getComponentTypeName() {
171         return "RemoteFieldsHolder";
172     }
173 
174     /**
175      * Path to the Map property that the translated fields bind to
176      *
177      * <p>
178      * It is assumed this property points to a Map where the property names on the returned remotable fields
179      * are keys in that map, with the corresponding map value giving the model value for the field
180      * </p>
181      *
182      * @return path to property on model
183      */
184     @BeanTagAttribute(name = "propertyName")
185     public String getPropertyName() {
186         return propertyName;
187     }
188 
189     /**
190      * Setter for the property name that points to the binding Map
191      *
192      * @param propertyName
193      */
194     public void setPropertyName(String propertyName) {
195         this.propertyName = propertyName;
196     }
197 
198     /**
199      * Can be used to for more complex binding paths
200      *
201      * <p>
202      * Generally not necessary to set on a field level, any default object path or binding prefixes set
203      * on the view or container will be inherited
204      * </p>
205      *
206      * @return BindingInfo instance containing binding information for the Map property
207      */
208     @BeanTagAttribute(name = "bindingInfo", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
209     public BindingInfo getBindingInfo() {
210         return bindingInfo;
211     }
212 
213     /**
214      * Setter for the Map property binding info instance
215      *
216      * @param bindingInfo
217      */
218     public void setBindingInfo(BindingInfo bindingInfo) {
219         this.bindingInfo = bindingInfo;
220     }
221 
222     /**
223      * Name of the method to invoke for retrieving the list of remotable fields
224      *
225      * <p>
226      * When only the fetching method to call is configured it is assumed to be a valid method on the view
227      * helper service for the containing view. The method name must accept the view, model object, and parent
228      * container as arguments, and return a list of {@link RemotableAttributeField} instances.
229      * </p>
230      *
231      * <p>
232      * For invoking the method on classes other than the view helper service, see {@link #getFetchingMethodInvoker()}
233      * </p>
234      *
235      * @return name of method to invoke for fetching remote fields
236      */
237     @BeanTagAttribute(name = "fetchingMethodToCall")
238     public String getFetchingMethodToCall() {
239         return fetchingMethodToCall;
240     }
241 
242     /**
243      * Setter for the fetching method to call
244      *
245      * @param fetchingMethodToCall
246      */
247     public void setFetchingMethodToCall(String fetchingMethodToCall) {
248         this.fetchingMethodToCall = fetchingMethodToCall;
249     }
250 
251     /**
252      * Configuration for the method to invoke for retrieving the list of remotable fields
253      *
254      * <p>
255      * Through the method invoker config, a service or static class can be configured along with the
256      * method name that will be invoked. The method name must accept the view, model object, and parent
257      * container as arguments, and return a list of {@link RemotableAttributeField} instances.
258      * </p>
259      *
260      * <p>
261      * Note the {@link org.kuali.rice.krad.uif.component.MethodInvokerConfig#getTargetMethod()} property can
262      * be configured, or the {@link #getFetchingMethodToCall()}. In the case of both configurations, the target
263      * method on the method invoker config will be used
264      * </p>
265      *
266      * @return MethodInvokerConfig instance containing method configuration
267      */
268     @BeanTagAttribute(name = "fetchingMethodInvoker", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
269     public MethodInvokerConfig getFetchingMethodInvoker() {
270         return fetchingMethodInvoker;
271     }
272 
273     /**
274      * Setter for the fetching method to invoke configuration
275      *
276      * @param fetchingMethodInvoker
277      */
278     public void setFetchingMethodInvoker(MethodInvokerConfig fetchingMethodInvoker) {
279         this.fetchingMethodInvoker = fetchingMethodInvoker;
280     }
281 
282     /**
283      * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
284      */
285     @Override
286     protected <T> void copyProperties(T component) {
287         super.copyProperties(component);
288         RemoteFieldsHolder remoteFieldsHolderCopy = (RemoteFieldsHolder) component;
289         remoteFieldsHolderCopy.setPropertyName(this.propertyName);
290 
291         if(this.bindingInfo != null) {
292             remoteFieldsHolderCopy.setBindingInfo((BindingInfo)this.bindingInfo.copy());
293         }
294 
295         remoteFieldsHolderCopy.setFetchingMethodToCall(this.fetchingMethodToCall);
296 
297         if(this.fetchingMethodInvoker != null) {
298             this.setFetchingMethodInvoker(CloneUtils.deepClone(this.fetchingMethodInvoker));
299         }
300     }
301 
302 }