View Javadoc

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.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.property.ConfigurationService;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.core.api.uif.DataType;
22  import org.kuali.rice.core.api.uif.RemotableAbstractControl;
23  import org.kuali.rice.core.api.uif.RemotableAbstractWidget;
24  import org.kuali.rice.core.api.uif.RemotableAttributeField;
25  import org.kuali.rice.core.api.uif.RemotableCheckbox;
26  import org.kuali.rice.core.api.uif.RemotableCheckboxGroup;
27  import org.kuali.rice.core.api.uif.RemotableHiddenInput;
28  import org.kuali.rice.core.api.uif.RemotableQuickFinder;
29  import org.kuali.rice.core.api.uif.RemotableRadioButtonGroup;
30  import org.kuali.rice.core.api.uif.RemotableSelect;
31  import org.kuali.rice.core.api.uif.RemotableTextInput;
32  import org.kuali.rice.core.api.uif.RemotableTextarea;
33  import org.kuali.rice.core.api.util.KeyValue;
34  import org.kuali.rice.krad.bo.BusinessObject;
35  import org.kuali.rice.krad.bo.DataObjectRelationship;
36  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
37  import org.kuali.rice.krad.service.DataDictionaryRemoteFieldService;
38  import org.kuali.rice.krad.service.DataDictionaryService;
39  import org.kuali.rice.krad.service.DataObjectMetaDataService;
40  import org.kuali.rice.krad.service.KRADServiceLocator;
41  import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
42  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
43  import org.kuali.rice.krad.uif.control.CheckboxControl;
44  import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
45  import org.kuali.rice.krad.uif.control.Control;
46  import org.kuali.rice.krad.uif.control.GroupControl;
47  import org.kuali.rice.krad.uif.control.HiddenControl;
48  import org.kuali.rice.krad.uif.control.MultiValueControl;
49  import org.kuali.rice.krad.uif.control.RadioGroupControl;
50  import org.kuali.rice.krad.uif.control.SelectControl;
51  import org.kuali.rice.krad.uif.control.TextAreaControl;
52  import org.kuali.rice.krad.uif.control.TextControl;
53  import org.kuali.rice.krad.uif.control.UserControl;
54  import org.kuali.rice.krad.util.KRADConstants;
55  import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
56  
57  import java.util.Collections;
58  import java.util.HashMap;
59  import java.util.List;
60  import java.util.Map;
61  
62  /**
63   * Implementation of the {@link DataDictionaryRemoteFieldService} service
64   *
65   * @author Kuali Rice Team (rice.collab@kuali.org)
66   */
67  public class DataDictionaryRemoteFieldServiceImpl implements DataDictionaryRemoteFieldService {
68  
69      /**
70       * @see org.kuali.rice.krad.service.DataDictionaryRemoteFieldService#buildRemotableFieldFromAttributeDefinition(java.lang.String,
71       *      java.lang.String)
72       */
73      public RemotableAttributeField buildRemotableFieldFromAttributeDefinition(String componentClassName,
74              String attributeName) {
75          AttributeDefinition baseDefinition;
76          Class<?> componentClass;
77          // try to resolve the component name - if not possible - try to pull the definition from the app mediation service
78          try {
79              componentClass = (Class<? extends BusinessObject>) Class.forName(componentClassName);
80              baseDefinition = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(componentClassName)
81                      .getAttributeDefinition(attributeName);
82          } catch (ClassNotFoundException ex) {
83              throw new RiceRuntimeException("Unable to find attribute definition for attribute : " + attributeName);
84          }
85  
86          RemotableAttributeField.Builder definition = RemotableAttributeField.Builder.create(baseDefinition.getName());
87  
88          definition.setLongLabel(baseDefinition.getLabel());
89          definition.setShortLabel(baseDefinition.getShortLabel());
90          definition.setMaxLength(baseDefinition.getMaxLength());
91          definition.setRequired(baseDefinition.isRequired());
92          definition.setForceUpperCase(baseDefinition.getForceUppercase());
93          //set the datatype - needed for successful custom doc searches
94          WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal
95                  .getWorkflowAttributePropertyResolutionService();
96          String dataType = propertyResolutionService.determineFieldDataType(
97                  (Class<? extends BusinessObject>) componentClass, attributeName);
98          definition.setDataType(DataType.valueOf(dataType.toUpperCase()));
99          RemotableAbstractControl.Builder control = createControl(baseDefinition);
100         if (control != null) {
101             definition.setControl(control);
102         }
103 
104         RemotableQuickFinder.Builder qf = createQuickFinder(componentClass, attributeName);
105         if (qf != null) {
106             definition.setWidgets(Collections.<RemotableAbstractWidget.Builder>singletonList(qf));
107         }
108 
109         return definition.build();
110     }
111 
112     /**
113      * Creates a {@link RemotableAbstractControl} instance based on the control definition within the given
114      * attribute definition
115      *
116      * @param attr - attribute definition instance to pull control from
117      * @return RemotableAbstractControl instance or null if one could not be built
118      */
119     protected RemotableAbstractControl.Builder createControl(AttributeDefinition attr) {
120         Control control = attr.getControlField();
121 
122         if (control != null) {
123             if (control instanceof CheckboxControl) {
124                 return RemotableCheckbox.Builder.create();
125             } else if (control instanceof CheckboxGroupControl) {
126                 return RemotableCheckboxGroup.Builder.create(getValues(attr));
127             } else if (control instanceof HiddenControl) {
128                 return RemotableHiddenInput.Builder.create();
129             } else if (control instanceof SelectControl) {
130                 RemotableSelect.Builder b = RemotableSelect.Builder.create(getValues(attr));
131                 b.setMultiple(((SelectControl) control).isMultiple());
132                 b.setSize(((SelectControl) control).getSize());
133             } else if (control instanceof RadioGroupControl) {
134                 return RemotableRadioButtonGroup.Builder.create(getValues(attr));
135             } else if (control instanceof TextControl) {
136                 final RemotableTextInput.Builder b = RemotableTextInput.Builder.create();
137                 b.setSize(((TextControl) control).getSize());
138                 return b;
139             } else if (control instanceof UserControl) {
140                 final RemotableTextInput.Builder b = RemotableTextInput.Builder.create();
141                 b.setSize(((UserControl) control).getSize());
142                 return b;
143             } else if (control instanceof GroupControl) {
144                 final RemotableTextInput.Builder b = RemotableTextInput.Builder.create();
145                 b.setSize(((GroupControl) control).getSize());
146                 return b;
147             } else if (control instanceof TextAreaControl) {
148                 final RemotableTextarea.Builder b = RemotableTextarea.Builder.create();
149                 b.setCols(((TextAreaControl) control).getCols());
150                 b.setRows(((TextAreaControl) control).getRows());
151                 return b;
152             }
153         }
154 
155         return null;
156     }
157 
158     /**
159      * Will first try to retrieve options configured on the control.  If that doesn't return any values then will
160      * try to use the optionfinder on the AttributeDefinition.
161      *
162      * @param attr - AttributeDefinition
163      * @return Map of key value pairs
164      */
165     protected Map<String, String> getValues(AttributeDefinition attr) {
166         Control control = attr.getControlField();
167 
168         if ((control instanceof MultiValueControl)
169                 && (((MultiValueControl) control).getOptions() != null)
170                 && !((MultiValueControl) control).getOptions().isEmpty()) {
171             List<KeyValue> keyValues = ((MultiValueControl) control).getOptions();
172             Map<String, String> options = new HashMap<String, String>();
173             for (KeyValue keyValue : keyValues) {
174                 options.put(keyValue.getKey(), keyValue.getValue());
175             }
176             return options;
177         } else if (attr.getOptionsFinder() != null) {
178             return attr.getOptionsFinder().getKeyLabelMap();
179         }
180 
181         return Collections.emptyMap();
182     }
183 
184     /**
185      * Builds a {@link RemotableQuickFinder} instance for the given attribute based on determined relationships
186      *
187      * <p>
188      * Uses the {@link DataObjectMetaDataService} to find relationships the given attribute participates in within the
189      * given class. If a relationship is not found, the title attribute is also checked to determine if a lookup should
190      * be rendered back to the component class itself. If a relationship suitable for lookup is found, the associated
191      * field conversions and lookup parameters are built
192      * </p>
193      *
194      * @param componentClass - class that attribute belongs to and should be checked for relationships
195      * @param attributeName - name of the attribute to determine quickfinder for
196      * @return RemotableQuickFinder.Builder instance for the configured lookup, or null if one could not be found
197      */
198     protected RemotableQuickFinder.Builder createQuickFinder(Class<?> componentClass, String attributeName) {
199         Object sampleComponent;
200         try {
201             sampleComponent = componentClass.newInstance();
202         } catch (InstantiationException e) {
203             throw new RiceRuntimeException(e);
204         } catch (IllegalAccessException e) {
205             throw new RiceRuntimeException(e);
206         }
207 
208         String lookupClassName = null;
209         Map<String, String> fieldConversions = new HashMap<String, String>();
210         Map<String, String> lookupParameters = new HashMap<String, String>();
211 
212         DataObjectRelationship relationship = getDataObjectMetaDataService().getDataObjectRelationship(sampleComponent,
213                 componentClass, attributeName, "", true, true, false);
214         if (relationship != null) {
215             lookupClassName = relationship.getRelatedClass().getName();
216 
217             for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
218                 String fromField = entry.getValue();
219                 String toField = entry.getKey();
220                 fieldConversions.put(fromField, toField);
221             }
222 
223             for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
224                 String fromField = entry.getKey();
225                 String toField = entry.getValue();
226 
227                 if (relationship.getUserVisibleIdentifierKey() == null || relationship.getUserVisibleIdentifierKey()
228                         .equals(fromField)) {
229                     lookupParameters.put(fromField, toField);
230                 }
231             }
232         } else {
233             // check for title attribute and if match build lookup to component class using pk fields
234             String titleAttribute = getDataObjectMetaDataService().getTitleAttribute(componentClass);
235             if (StringUtils.equals(titleAttribute, attributeName)) {
236                 lookupClassName = componentClass.getName();
237 
238                 List<String> pkAttributes = getDataObjectMetaDataService().listPrimaryKeyFieldNames(componentClass);
239                 for (String pkAttribute : pkAttributes) {
240                     fieldConversions.put(pkAttribute, pkAttribute);
241                     if (!StringUtils.equals(pkAttribute, attributeName)) {
242                         lookupParameters.put(pkAttribute, pkAttribute);
243                     }
244                 }
245             }
246         }
247 
248         if (StringUtils.isNotBlank(lookupClassName)) {
249             String baseUrl = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.KRAD_LOOKUP_URL_KEY);
250             RemotableQuickFinder.Builder builder = RemotableQuickFinder.Builder.create(baseUrl, lookupClassName);
251             builder.setLookupParameters(lookupParameters);
252             builder.setFieldConversions(fieldConversions);
253 
254             return builder;
255         }
256 
257         return null;
258     }
259 
260     protected DataDictionaryService getDataDictionaryService() {
261         return KRADServiceLocatorWeb.getDataDictionaryService();
262     }
263 
264     protected DataObjectMetaDataService getDataObjectMetaDataService() {
265         return KRADServiceLocatorWeb.getDataObjectMetaDataService();
266     }
267 
268     protected ConfigurationService getKualiConfigurationService() {
269         return KRADServiceLocator.getKualiConfigurationService();
270     }
271 }