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 }