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