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 }