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 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<AttributeField> 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 String 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 String 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 }