1 /**
2 * Copyright 2005-2012 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 }