1 /**
2 * Copyright 2005-2015 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.ComponentFactory;
32 import org.kuali.rice.krad.uif.util.CopyUtils;
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 = "remotableFieldsPlaceholder", 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 = CopyUtils.copy(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
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
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
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(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 }