001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.krad.service.impl;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.core.api.config.property.ConfigurationService;
020    import org.kuali.rice.core.api.exception.RiceRuntimeException;
021    import org.kuali.rice.core.api.uif.DataType;
022    import org.kuali.rice.core.api.uif.RemotableAbstractControl;
023    import org.kuali.rice.core.api.uif.RemotableAbstractWidget;
024    import org.kuali.rice.core.api.uif.RemotableAttributeField;
025    import org.kuali.rice.core.api.uif.RemotableCheckbox;
026    import org.kuali.rice.core.api.uif.RemotableCheckboxGroup;
027    import org.kuali.rice.core.api.uif.RemotableHiddenInput;
028    import org.kuali.rice.core.api.uif.RemotableQuickFinder;
029    import org.kuali.rice.core.api.uif.RemotableRadioButtonGroup;
030    import org.kuali.rice.core.api.uif.RemotableSelect;
031    import org.kuali.rice.core.api.uif.RemotableTextInput;
032    import org.kuali.rice.core.api.uif.RemotableTextarea;
033    import org.kuali.rice.core.api.util.KeyValue;
034    import org.kuali.rice.krad.bo.BusinessObject;
035    import org.kuali.rice.krad.bo.DataObjectRelationship;
036    import org.kuali.rice.krad.datadictionary.AttributeDefinition;
037    import org.kuali.rice.krad.service.DataDictionaryRemoteFieldService;
038    import org.kuali.rice.krad.service.DataDictionaryService;
039    import org.kuali.rice.krad.service.DataObjectMetaDataService;
040    import org.kuali.rice.krad.service.KRADServiceLocator;
041    import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
042    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
043    import org.kuali.rice.krad.uif.control.CheckboxControl;
044    import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
045    import org.kuali.rice.krad.uif.control.Control;
046    import org.kuali.rice.krad.uif.control.GroupControl;
047    import org.kuali.rice.krad.uif.control.HiddenControl;
048    import org.kuali.rice.krad.uif.control.MultiValueControl;
049    import org.kuali.rice.krad.uif.control.RadioGroupControl;
050    import org.kuali.rice.krad.uif.control.SelectControl;
051    import org.kuali.rice.krad.uif.control.TextAreaControl;
052    import org.kuali.rice.krad.uif.control.TextControl;
053    import org.kuali.rice.krad.uif.control.UserControl;
054    import org.kuali.rice.krad.util.KRADConstants;
055    import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
056    
057    import java.util.Collections;
058    import java.util.HashMap;
059    import java.util.List;
060    import java.util.Map;
061    
062    /**
063     * Implementation of the {@link DataDictionaryRemoteFieldService} service
064     *
065     * @author Kuali Rice Team (rice.collab@kuali.org)
066     */
067    public class DataDictionaryRemoteFieldServiceImpl implements DataDictionaryRemoteFieldService {
068    
069        /**
070         * @see org.kuali.rice.krad.service.DataDictionaryRemoteFieldService#buildRemotableFieldFromAttributeDefinition(java.lang.String,
071         *      java.lang.String)
072         */
073        public RemotableAttributeField buildRemotableFieldFromAttributeDefinition(String componentClassName,
074                String attributeName) {
075            AttributeDefinition baseDefinition;
076            Class<?> componentClass;
077            // try to resolve the component name - if not possible - try to pull the definition from the app mediation service
078            try {
079                componentClass = (Class<? extends BusinessObject>) Class.forName(componentClassName);
080                baseDefinition = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(componentClassName)
081                        .getAttributeDefinition(attributeName);
082            } catch (ClassNotFoundException ex) {
083                throw new RiceRuntimeException("Unable to find attribute definition for attribute : " + attributeName);
084            }
085    
086            RemotableAttributeField.Builder definition = RemotableAttributeField.Builder.create(baseDefinition.getName());
087    
088            definition.setLongLabel(baseDefinition.getLabel());
089            definition.setShortLabel(baseDefinition.getShortLabel());
090            definition.setMaxLength(baseDefinition.getMaxLength());
091    
092            if (baseDefinition.isRequired() != null) {
093                definition.setRequired(baseDefinition.isRequired());
094            }
095    
096            definition.setForceUpperCase(baseDefinition.getForceUppercase());
097            //set the datatype - needed for successful custom doc searches
098            WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal
099                    .getWorkflowAttributePropertyResolutionService();
100            String dataType = propertyResolutionService.determineFieldDataType(
101                    (Class<? extends BusinessObject>) componentClass, attributeName);
102            definition.setDataType(DataType.valueOf(dataType.toUpperCase()));
103            RemotableAbstractControl.Builder control = createControl(baseDefinition);
104            if (control != null) {
105                definition.setControl(control);
106            }
107    
108            RemotableQuickFinder.Builder qf = createQuickFinder(componentClass, attributeName);
109            if (qf != null) {
110                definition.setWidgets(Collections.<RemotableAbstractWidget.Builder>singletonList(qf));
111            }
112    
113            return definition.build();
114        }
115    
116        /**
117         * Creates a {@link RemotableAbstractControl} instance based on the control definition within the given
118         * attribute definition
119         *
120         * @param attr - attribute definition instance to pull control from
121         * @return RemotableAbstractControl instance or null if one could not be built
122         */
123        protected RemotableAbstractControl.Builder createControl(AttributeDefinition attr) {
124            Control control = attr.getControlField();
125    
126            if (control != null) {
127                if (control instanceof CheckboxControl) {
128                    return RemotableCheckbox.Builder.create();
129                } else if (control instanceof CheckboxGroupControl) {
130                    return RemotableCheckboxGroup.Builder.create(getValues(attr));
131                } else if (control instanceof HiddenControl) {
132                    return RemotableHiddenInput.Builder.create();
133                } else if (control instanceof SelectControl) {
134                    RemotableSelect.Builder b = RemotableSelect.Builder.create(getValues(attr));
135                    b.setMultiple(((SelectControl) control).isMultiple());
136                    b.setSize(((SelectControl) control).getSize());
137                } else if (control instanceof RadioGroupControl) {
138                    return RemotableRadioButtonGroup.Builder.create(getValues(attr));
139                } else if (control instanceof TextControl) {
140                    final RemotableTextInput.Builder b = RemotableTextInput.Builder.create();
141                    b.setSize(((TextControl) control).getSize());
142                    return b;
143                } else if (control instanceof UserControl) {
144                    final RemotableTextInput.Builder b = RemotableTextInput.Builder.create();
145                    b.setSize(((UserControl) control).getSize());
146                    return b;
147                } else if (control instanceof GroupControl) {
148                    final RemotableTextInput.Builder b = RemotableTextInput.Builder.create();
149                    b.setSize(((GroupControl) control).getSize());
150                    return b;
151                } else if (control instanceof TextAreaControl) {
152                    final RemotableTextarea.Builder b = RemotableTextarea.Builder.create();
153                    b.setCols(((TextAreaControl) control).getCols());
154                    b.setRows(((TextAreaControl) control).getRows());
155                    return b;
156                }
157            }
158            
159            return null;
160        }
161    
162        /**
163         * Will first try to retrieve options configured on the control.  If that doesn't return any values then will
164         * try to use the optionfinder on the AttributeDefinition.
165         *
166         * @param attr - AttributeDefinition
167         * @return Map of key value pairs
168         */
169        protected Map<String, String> getValues(AttributeDefinition attr) {
170            Control control = attr.getControlField();
171    
172            if ((control instanceof MultiValueControl)
173                    && (((MultiValueControl) control).getOptions() != null)
174                    && !((MultiValueControl) control).getOptions().isEmpty()) {
175                List<KeyValue> keyValues = ((MultiValueControl) control).getOptions();
176                        Map<String, String> options = new HashMap<String, String> ();
177                        for (KeyValue keyValue : keyValues) {
178                            options.put(keyValue.getKey(), keyValue.getValue());
179                        }
180                        return options;
181            } else if (attr.getOptionsFinder() != null) {
182                return attr.getOptionsFinder().getKeyLabelMap();
183            }
184    
185            return Collections.emptyMap();
186        }
187    
188        /**
189         * Builds a {@link RemotableQuickFinder} instance for the given attribute based on determined relationships
190         *
191         * <p>
192         * Uses the {@link DataObjectMetaDataService} to find relationships the given attribute participates in within the
193         * given class. If a relationship is not found, the title attribute is also checked to determine if a lookup should
194         * be rendered back to the component class itself. If a relationship suitable for lookup is found, the associated
195         * field conversions and lookup parameters are built
196         * </p>
197         *
198         * @param componentClass - class that attribute belongs to and should be checked for relationships
199         * @param attributeName - name of the attribute to determine quickfinder for
200         * @return RemotableQuickFinder.Builder instance for the configured lookup, or null if one could not be found
201         */
202        protected RemotableQuickFinder.Builder createQuickFinder(Class<?> componentClass, String attributeName) {
203            Object sampleComponent;
204            try {
205                sampleComponent = componentClass.newInstance();
206            } catch (InstantiationException e) {
207                throw new RiceRuntimeException(e);
208            } catch (IllegalAccessException e) {
209                throw new RiceRuntimeException(e);
210            }
211    
212            String lookupClassName = null;
213            Map<String, String> fieldConversions = new HashMap<String, String>();
214            Map<String, String> lookupParameters = new HashMap<String, String>();
215    
216            DataObjectRelationship relationship = getDataObjectMetaDataService().getDataObjectRelationship(sampleComponent,
217                    componentClass, attributeName, "", true, true, false);
218            if (relationship != null) {
219                lookupClassName = relationship.getRelatedClass().getName();
220    
221                for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
222                    String fromField = entry.getValue();
223                    String toField = entry.getKey();
224                    fieldConversions.put(fromField, toField);
225                }
226    
227                for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
228                    String fromField = entry.getKey();
229                    String toField = entry.getValue();
230    
231                    if (relationship.getUserVisibleIdentifierKey() == null || relationship.getUserVisibleIdentifierKey()
232                            .equals(fromField)) {
233                        lookupParameters.put(fromField, toField);
234                    }
235                }
236            } else {
237                // check for title attribute and if match build lookup to component class using pk fields
238                String titleAttribute = getDataObjectMetaDataService().getTitleAttribute(componentClass);
239                if (StringUtils.equals(titleAttribute, attributeName)) {
240                    lookupClassName = componentClass.getName();
241    
242                    List<String> pkAttributes = getDataObjectMetaDataService().listPrimaryKeyFieldNames(componentClass);
243                    for (String pkAttribute : pkAttributes) {
244                        fieldConversions.put(pkAttribute, pkAttribute);
245                        if (!StringUtils.equals(pkAttribute, attributeName)) {
246                            lookupParameters.put(pkAttribute, pkAttribute);
247                        }
248                    }
249                }
250            }
251            
252            if (StringUtils.isNotBlank(lookupClassName)) {
253                String baseUrl = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.KRAD_LOOKUP_URL_KEY);
254                RemotableQuickFinder.Builder builder = RemotableQuickFinder.Builder.create(baseUrl, lookupClassName);
255                builder.setLookupParameters(lookupParameters);
256                builder.setFieldConversions(fieldConversions);
257    
258                return builder;
259            }
260    
261            return null;
262        }
263    
264        protected DataDictionaryService getDataDictionaryService() {
265            return KRADServiceLocatorWeb.getDataDictionaryService();
266        }
267    
268        protected DataObjectMetaDataService getDataObjectMetaDataService() {
269            return KRADServiceLocatorWeb.getDataObjectMetaDataService();
270        }
271    
272        protected ConfigurationService getKualiConfigurationService() {
273            return KRADServiceLocator.getKualiConfigurationService();
274        }
275    }