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.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.config.property.ConfigurationService;
020import org.kuali.rice.core.api.exception.RiceRuntimeException;
021import org.kuali.rice.core.api.uif.DataType;
022import org.kuali.rice.core.api.uif.RemotableAbstractControl;
023import org.kuali.rice.core.api.uif.RemotableAbstractWidget;
024import org.kuali.rice.core.api.uif.RemotableAttributeField;
025import org.kuali.rice.core.api.uif.RemotableCheckbox;
026import org.kuali.rice.core.api.uif.RemotableCheckboxGroup;
027import org.kuali.rice.core.api.uif.RemotableHiddenInput;
028import org.kuali.rice.core.api.uif.RemotableQuickFinder;
029import org.kuali.rice.core.api.uif.RemotableRadioButtonGroup;
030import org.kuali.rice.core.api.uif.RemotableSelect;
031import org.kuali.rice.core.api.uif.RemotableTextInput;
032import org.kuali.rice.core.api.uif.RemotableTextarea;
033import org.kuali.rice.core.api.util.KeyValue;
034import org.kuali.rice.krad.bo.BusinessObject;
035import org.kuali.rice.krad.bo.DataObjectRelationship;
036import org.kuali.rice.krad.datadictionary.AttributeDefinition;
037import org.kuali.rice.krad.service.DataDictionaryRemoteFieldService;
038import org.kuali.rice.krad.service.DataDictionaryService;
039import org.kuali.rice.krad.service.DataObjectMetaDataService;
040import org.kuali.rice.krad.service.KRADServiceLocator;
041import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
042import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
043import org.kuali.rice.krad.uif.control.CheckboxControl;
044import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
045import org.kuali.rice.krad.uif.control.Control;
046import org.kuali.rice.krad.uif.control.GroupControl;
047import org.kuali.rice.krad.uif.control.HiddenControl;
048import org.kuali.rice.krad.uif.control.MultiValueControl;
049import org.kuali.rice.krad.uif.control.RadioGroupControl;
050import org.kuali.rice.krad.uif.control.SelectControl;
051import org.kuali.rice.krad.uif.control.TextAreaControl;
052import org.kuali.rice.krad.uif.control.TextControl;
053import org.kuali.rice.krad.uif.control.UserControl;
054import org.kuali.rice.krad.util.KRADConstants;
055import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
056
057import java.util.Collections;
058import java.util.HashMap;
059import java.util.List;
060import java.util.Map;
061
062/**
063 * Implementation of the {@link DataDictionaryRemoteFieldService} service
064 *
065 * @author Kuali Rice Team (rice.collab@kuali.org)
066 */
067public 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        definition.setRequired(baseDefinition.isRequired());
092        definition.setForceUpperCase(baseDefinition.getForceUppercase());
093        //set the datatype - needed for successful custom doc searches
094        WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal
095                .getWorkflowAttributePropertyResolutionService();
096        String dataType = propertyResolutionService.determineFieldDataType(
097                (Class<? extends BusinessObject>) componentClass, attributeName);
098        definition.setDataType(DataType.valueOf(dataType.toUpperCase()));
099        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}