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