001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.field; 017 018import java.util.ArrayList; 019import java.util.List; 020 021import org.apache.commons.lang.StringUtils; 022import org.kuali.rice.core.api.uif.RemotableAttributeField; 023import org.kuali.rice.krad.datadictionary.parse.BeanTag; 024import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 025import org.kuali.rice.krad.uif.component.BindingInfo; 026import org.kuali.rice.krad.uif.component.ComponentBase; 027import org.kuali.rice.krad.uif.component.DataBinding; 028import org.kuali.rice.krad.uif.component.MethodInvokerConfig; 029import org.kuali.rice.krad.uif.container.Container; 030import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 031import org.kuali.rice.krad.uif.util.ComponentFactory; 032import org.kuali.rice.krad.uif.util.CopyUtils; 033 034/** 035 * A placeholder in the configuration for a <code>Container</code> list of items that will be invoked to 036 * retrieve a list of {@link RemotableAttributeField} instances which will then be inserted into the containers 037 * list at the position of the holder 038 * 039 * <p> 040 * Since remotable fields are dynamic by nature, the individual fields cannot be configured initially with the 041 * container. Further more the properties for the field are constructed with code. This gives the ability to specify 042 * where that list of fields should be placed, along with configured on how to retrieve the remote fields. 043 * </p> 044 * 045 * <p> 046 * The fetching properties are used to configure what method to invoke that will return the list of remotable fields. 047 * Specifying the {@link #getFetchingMethodToCall()} only assumes the method is on the view helper service for the 048 * contained view. For invoking other classes, such as services or static classes, use {@link 049 * #getFetchingMethodInvoker()} 050 * </p> 051 * 052 * <p> 053 * The list of remotable fields should bind to a Map property on the model. The {@link #getPropertyName()} and 054 * {@link #getBindingInfo()} properties specify the path to this property. The property names configured on the 055 * returned fields are assumed to be keys in that above configured map, with the corresponding map value giving the 056 * actual model value for the remote field. 057 * </p> 058 * 059 * <p> 060 * e.g. configuration 061 * {@code 062 * <property name="items"> 063 * <list> 064 * <bean parent="RemoteFieldsHolder" p:propertyName="remoteFieldValuesMap" 065 * p:fetchingMethodToCall="retrieveRemoteFields"/> 066 * ... 067 * } 068 * 069 * This example will invoke a method named 'retrieveRemoteFields' on the view helper service, which should return 070 * a list of {@link RemotableAttributeField} instances. The view, model instance, and parent container will be sent 071 * to the method as arguments. 072 * 073 * The returned fields will be translated to {@link InputField} instances that bind to a map property named 074 * 'remoteFieldValuesMap' on the model. 075 * </p> 076 * 077 * @author Kuali Rice Team (rice.collab@kuali.org) 078 */ 079@BeanTag(name = "remotableFieldsPlaceholder", parent = "Uif-RemotableFieldsPlaceholderConfig") 080public class RemoteFieldsHolder extends ComponentBase implements DataBinding { 081 private static final long serialVersionUID = -8493923312021633727L; 082 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RemoteFieldsHolder.class); 083 084 private String propertyName; 085 private BindingInfo bindingInfo; 086 087 private String fetchingMethodToCall; 088 private MethodInvokerConfig fetchingMethodInvoker; 089 090 public RemoteFieldsHolder() { 091 super(); 092 } 093 094 /** 095 * Invokes the configured fetching method to retrieve a list of remotable fields, then invoked the 096 * {@code ComponentFactory} to translate the fields, and finally sets up the binding for the attribute fields 097 * 098 * @param parent container instance that holder is configured for, sent to the fetching method 099 * @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}