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 org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.uif.RemotableAttributeField;
020import org.kuali.rice.krad.uif.component.BindingInfo;
021import org.kuali.rice.krad.uif.component.ComponentBase;
022import org.kuali.rice.krad.uif.component.DataBinding;
023import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
024import org.kuali.rice.krad.uif.container.Container;
025import org.kuali.rice.krad.uif.util.CloneUtils;
026import org.kuali.rice.krad.uif.util.ComponentFactory;
027import org.kuali.rice.krad.uif.view.View;
028
029import java.util.ArrayList;
030import java.util.List;
031
032/**
033 * A placeholder in the configuration for a <code>Container</code> list of items that will be invoked to
034 * retrieve a list of {@link RemotableAttributeField} instances which will then be inserted into the containers
035 * list at the position of the holder
036 *
037 * <p>
038 * Since remotable fields are dynamic by nature, the individual fields cannot be configured initially with the
039 * container. Further more the properties for the field are constructed with code. This gives the ability to specify
040 * where that list of fields should be placed, along with configured on how to retrieve the remote fields.
041 * </p>
042 *
043 * <p>
044 * The fetching properties are used to configure what method to invoke that will return the list of remotable fields.
045 * Specifying the {@link #getFetchingMethodToCall()} only assumes the method is on the view helper service for the
046 * contained view. For invoking other classes, such as services or static classes, use {@link
047 * #getFetchingMethodInvoker()}
048 * </p>
049 *
050 * <p>
051 * The list of remotable fields should bind to a Map property on the model. The {@link #getPropertyName()} and
052 * {@link #getBindingInfo()} properties specify the path to this property. The property names configured on the
053 * returned fields are assumed to be keys in that above configured map, with the corresponding map value giving the
054 * actual model value for the remote field.
055 * </p>
056 *
057 * <p>
058 * e.g. configuration
059 * {@code
060 *    <property name="items">
061      <list>
062        <bean parent="RemoteFieldsHolder" p:propertyName="remoteFieldValuesMap"
063              p:fetchingMethodToCall="retrieveRemoteFields"/>
064 *    ...
065 * }
066 *
067 * This example will invoke a method named 'retrieveRemoteFields' on the view helper service, which should return
068 * a list of {@link RemotableAttributeField} instances. The view, model instance, and parent container will be sent
069 * to the method as arguments.
070 *
071 * The returned fields will be translated to {@link InputField} instances that bind to a map property named
072 * 'remoteFieldValuesMap' on the model.
073 * </p>
074 *
075 * @author Kuali Rice Team (rice.collab@kuali.org)
076 */
077public class RemoteFieldsHolder extends ComponentBase implements DataBinding {
078    private static final long serialVersionUID = -8493923312021633727L;
079    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RemoteFieldsHolder.class);
080
081    private String propertyName;
082    private BindingInfo bindingInfo;
083
084    private String fetchingMethodToCall;
085    private MethodInvokerConfig fetchingMethodInvoker;
086
087    public RemoteFieldsHolder() {
088        super();
089    }
090
091    /**
092     * Invokes the configured fetching method to retrieve a list of remotable fields, then invoked the
093     * {@code ComponentFactory} to translate the fields, and finally sets up the binding for the attribute fields
094     *
095     * @param view - view instance the container belongs to, sent to the fetching method
096     * @param model - object containing the view data, sent to the fetching method
097     * @param parent - container instance that holder is configured for, sent to the fetching method
098     * @return List<AttributeField> list of attribute fields that should be placed into container, if no remotable
099     * 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}