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}