001 /**
002 * Copyright 2005-2011 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.inquiry;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.kuali.rice.core.api.CoreApiServiceLocator;
020 import org.kuali.rice.core.api.config.property.ConfigurationService;
021 import org.kuali.rice.core.api.encryption.EncryptionService;
022 import org.kuali.rice.krad.bo.BusinessObject;
023 import org.kuali.rice.krad.bo.DataObjectRelationship;
024 import org.kuali.rice.krad.bo.DocumentHeader;
025 import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
026 import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
027 import org.kuali.rice.krad.service.BusinessObjectService;
028 import org.kuali.rice.krad.service.DataDictionaryService;
029 import org.kuali.rice.krad.service.DataObjectAuthorizationService;
030 import org.kuali.rice.krad.service.DataObjectMetaDataService;
031 import org.kuali.rice.krad.service.KRADServiceLocator;
032 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
033 import org.kuali.rice.krad.service.KualiModuleService;
034 import org.kuali.rice.krad.service.ModuleService;
035 import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
036 import org.kuali.rice.krad.uif.widget.Inquiry;
037 import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
038 import org.kuali.rice.krad.util.KRADConstants;
039 import org.kuali.rice.krad.util.ObjectUtils;
040
041 import java.security.GeneralSecurityException;
042 import java.util.ArrayList;
043 import java.util.Collections;
044 import java.util.HashMap;
045 import java.util.List;
046 import java.util.Map;
047
048 /**
049 * Implementation of the <code>Inquirable</code> interface that uses metadata
050 * from the data dictionary and performs a query against the database to retrieve
051 * the data object for inquiry
052 *
053 * <p>
054 * More advanced lookup operations or alternate ways of retrieving metadata can
055 * be implemented by extending this base implementation and configuring
056 * </p>
057 *
058 * @author Kuali Rice Team (rice.collab@kuali.org)
059 */
060 public class InquirableImpl extends ViewHelperServiceImpl implements Inquirable {
061 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InquirableImpl.class);
062
063 protected Class<?> dataObjectClass;
064
065 /**
066 * A list that can be used to define classes that are superclasses or
067 * superinterfaces of kuali objects where those objects' inquiry URLs need
068 * to use the name of the superclass or superinterface as the business
069 * object class attribute
070 */
071 public static List<Class<?>> SUPER_CLASS_TRANSLATOR_LIST = new ArrayList<Class<?>>();
072
073 /**
074 * Finds primary and alternate key sets configured for the configured data object class and
075 * then attempts to find a set with matching key/value pairs from the request, if a set is
076 * found then calls the module service (for EBOs) or business object service to retrieve
077 * the data object
078 *
079 * <p>
080 * Note at this point on business objects are supported by the default implementation
081 * </p>
082 *
083 * @see Inquirable#retrieveDataObject(java.util.Map<java.lang.String,java.lang.String>)
084 */
085 @Override
086 public Object retrieveDataObject(Map<String, String> parameters) {
087 if (dataObjectClass == null) {
088 LOG.error("Data object class must be set in inquirable before retrieving the object");
089 throw new RuntimeException("Data object class must be set in inquirable before retrieving the object");
090 }
091
092 // build list of key values from the map parameters
093 List<String> pkPropertyNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass);
094
095 // some classes might have alternate keys defined for retrieving
096 List<List<String>> alternateKeyNames = this.getAlternateKeysForClass(dataObjectClass);
097
098 // add pk set as beginning so it will be checked first for match
099 alternateKeyNames.add(0, pkPropertyNames);
100
101 List<String> dataObjectKeySet = retrieveKeySetFromMap(alternateKeyNames, parameters);
102 if ((dataObjectKeySet == null) || dataObjectKeySet.isEmpty()) {
103 LOG.warn("Matching key set not found in request for class: " + getDataObjectClass());
104
105 return null;
106 }
107
108 // found key set, now build map of key values pairs we can use to retrieve the object
109 Map<String, Object> keyPropertyValues = new HashMap<String, Object>();
110 for (String keyPropertyName : dataObjectKeySet) {
111 String keyPropertyValue = parameters.get(keyPropertyName);
112
113 // uppercase value if needed
114 Boolean forceUppercase = Boolean.FALSE;
115 try {
116 forceUppercase = getDataDictionaryService().getAttributeForceUppercase(dataObjectClass,
117 keyPropertyName);
118 } catch (UnknownBusinessClassAttributeException ex) {
119 // swallowing exception because this check for ForceUppercase would
120 // require a DD entry for the attribute, and we will just set force uppercase to false
121 LOG.warn("Data object class "
122 + dataObjectClass
123 + " property "
124 + keyPropertyName
125 + " should probably have a DD definition.", ex);
126 }
127
128 if (forceUppercase.booleanValue() && (keyPropertyValue != null)) {
129 keyPropertyValue = keyPropertyValue.toUpperCase();
130 }
131
132 // check security on key field
133 if (getDataObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass,
134 keyPropertyName)) {
135 try {
136 keyPropertyValue = getEncryptionService().decrypt(keyPropertyValue);
137 } catch (GeneralSecurityException e) {
138 LOG.error("Data object class "
139 + dataObjectClass
140 + " property "
141 + keyPropertyName
142 + " should have been encrypted, but there was a problem decrypting it.", e);
143 throw new RuntimeException("Data object class "
144 + dataObjectClass
145 + " property "
146 + keyPropertyName
147 + " should have been encrypted, but there was a problem decrypting it.", e);
148 }
149 }
150
151 keyPropertyValues.put(keyPropertyName, keyPropertyValue);
152 }
153
154 // now retrieve the object based on the key set
155 Object dataObject = null;
156
157 ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
158 getDataObjectClass());
159 if (moduleService != null && moduleService.isExternalizable(getDataObjectClass())) {
160 dataObject = moduleService.getExternalizableBusinessObject(getDataObjectClass().asSubclass(
161 ExternalizableBusinessObject.class), keyPropertyValues);
162 } else if (BusinessObject.class.isAssignableFrom(getDataObjectClass())) {
163 dataObject = getBusinessObjectService().findByPrimaryKey(getDataObjectClass().asSubclass(
164 BusinessObject.class), keyPropertyValues);
165 }
166
167 return dataObject;
168 }
169
170 /**
171 * Iterates through the list of key sets looking for a set where the given map of parameters has
172 * all the key names and values are non-blank, first matched set is returned
173 *
174 * @param potentialKeySets - List of key sets to check for match
175 * @param parameters - map of parameter name/value pairs for matching key set
176 * @return List<String> key set that was matched, or null if none were matched
177 */
178 protected List<String> retrieveKeySetFromMap(List<List<String>> potentialKeySets, Map<String, String> parameters) {
179 List<String> foundKeySet = null;
180
181 for (List<String> potentialKeySet : potentialKeySets) {
182 boolean keySetMatch = true;
183 for (String keyName : potentialKeySet) {
184 if (!parameters.containsKey(keyName) || StringUtils.isBlank(parameters.get(keyName))) {
185 keySetMatch = false;
186 }
187 }
188
189 if (keySetMatch) {
190 foundKeySet = potentialKeySet;
191 break;
192 }
193 }
194
195 return foundKeySet;
196 }
197
198 /**
199 * Invokes the module service to retrieve any alternate keys that have been
200 * defined for the given class
201 *
202 * @param clazz - class to find alternate keys for
203 * @return List<List<String>> list of alternate key sets, or empty list if none are found
204 */
205 protected List<List<String>> getAlternateKeysForClass(Class<?> clazz) {
206 KualiModuleService kualiModuleService = getKualiModuleService();
207 ModuleService moduleService = kualiModuleService.getResponsibleModuleService(clazz);
208
209 List<List<String>> altKeys = null;
210 if (moduleService != null) {
211 altKeys = moduleService.listAlternatePrimaryKeyFieldNames(clazz);
212 }
213
214 return altKeys != null ? altKeys : new ArrayList<List<String>>();
215 }
216
217 /**
218 * @see Inquirable#buildInquirableLink(java.lang.Object,
219 * java.lang.String, org.kuali.rice.krad.uif.widget.Inquiry)
220 */
221 @Override
222 public void buildInquirableLink(Object dataObject, String propertyName, Inquiry inquiry) {
223 Class<?> inquiryObjectClass = null;
224
225 // inquiry into data object class if property is title attribute
226 Class<?> objectClass = ObjectUtils.materializeClassForProxiedObject(dataObject);
227 if (propertyName.equals(getDataObjectMetaDataService().getTitleAttribute(objectClass))) {
228 inquiryObjectClass = objectClass;
229 } else if (ObjectUtils.isNestedAttribute(propertyName)) {
230 String nestedPropertyName = ObjectUtils.getNestedAttributePrefix(propertyName);
231 Object nestedPropertyObject = ObjectUtils.getNestedValue(dataObject, nestedPropertyName);
232
233 if (ObjectUtils.isNotNull(nestedPropertyObject)) {
234 String nestedPropertyPrimitive = ObjectUtils.getNestedAttributePrimitive(propertyName);
235 Class<?> nestedPropertyObjectClass = ObjectUtils.materializeClassForProxiedObject(nestedPropertyObject);
236
237 if (nestedPropertyPrimitive.equals(getDataObjectMetaDataService().getTitleAttribute(
238 nestedPropertyObjectClass))) {
239 inquiryObjectClass = nestedPropertyObjectClass;
240 }
241 }
242 }
243
244 // if not title, then get primary relationship
245 DataObjectRelationship relationship = null;
246 if (inquiryObjectClass == null) {
247 relationship = getDataObjectMetaDataService().getDataObjectRelationship(dataObject, objectClass,
248 propertyName, "", true, false, true);
249 if (relationship != null) {
250 inquiryObjectClass = relationship.getRelatedClass();
251 }
252 }
253
254 // if haven't found inquiry class, then no inquiry can be rendered
255 if (inquiryObjectClass == null) {
256 inquiry.setRender(false);
257
258 return;
259 }
260
261 if (DocumentHeader.class.isAssignableFrom(inquiryObjectClass)) {
262 String documentNumber = (String) ObjectUtils.getPropertyValue(dataObject, propertyName);
263 if (StringUtils.isNotBlank(documentNumber)) {
264 inquiry.getInquiryLinkField().setHrefText(getConfigurationService().getPropertyValueAsString(
265 KRADConstants.WORKFLOW_URL_KEY)
266 + KRADConstants.DOCHANDLER_DO_URL
267 + documentNumber
268 + KRADConstants.DOCHANDLER_URL_CHUNK);
269 inquiry.getInquiryLinkField().setLinkLabel(documentNumber);
270 inquiry.setRender(true);
271 }
272
273 return;
274 }
275
276 synchronized (SUPER_CLASS_TRANSLATOR_LIST) {
277 for (Class<?> clazz : SUPER_CLASS_TRANSLATOR_LIST) {
278 if (clazz.isAssignableFrom(inquiryObjectClass)) {
279 inquiryObjectClass = clazz;
280 break;
281 }
282 }
283 }
284
285 if (!inquiryObjectClass.isInterface() && ExternalizableBusinessObject.class.isAssignableFrom(
286 inquiryObjectClass)) {
287 inquiryObjectClass = ExternalizableBusinessObjectUtils.determineExternalizableBusinessObjectSubInterface(
288 inquiryObjectClass);
289 }
290
291 // listPrimaryKeyFieldNames returns an unmodifiable list. So a copy is necessary.
292 List<String> keys = new ArrayList<String>(getDataObjectMetaDataService().listPrimaryKeyFieldNames(
293 inquiryObjectClass));
294
295 if (keys == null) {
296 keys = Collections.emptyList();
297 }
298
299 // build inquiry parameter mappings
300 Map<String, String> inquiryParameters = new HashMap<String, String>();
301 for (String keyName : keys) {
302 String keyConversion = keyName;
303 if (relationship != null) {
304 keyConversion = relationship.getParentAttributeForChildAttribute(keyName);
305 } else if (ObjectUtils.isNestedAttribute(propertyName)) {
306 String nestedAttributePrefix = ObjectUtils.getNestedAttributePrefix(propertyName);
307 keyConversion = nestedAttributePrefix + "." + keyName;
308 }
309
310 inquiryParameters.put(keyConversion, keyName);
311 }
312
313 inquiry.buildInquiryLink(dataObject, propertyName, inquiryObjectClass, inquiryParameters);
314 }
315
316 /**
317 * @see Inquirable#setDataObjectClass(java.lang.Class)
318 */
319 @Override
320 public void setDataObjectClass(Class<?> dataObjectClass) {
321 this.dataObjectClass = dataObjectClass;
322 }
323
324 /**
325 * Retrieves the data object class configured for this inquirable
326 *
327 * @return Class<?> of configured data object, or null if data object class not configured
328 */
329 protected Class<?> getDataObjectClass() {
330 return this.dataObjectClass;
331 }
332
333 protected ConfigurationService getConfigurationService() {
334 return KRADServiceLocator.getKualiConfigurationService();
335 }
336
337 protected DataObjectMetaDataService getDataObjectMetaDataService() {
338 return KRADServiceLocatorWeb.getDataObjectMetaDataService();
339 }
340
341 protected KualiModuleService getKualiModuleService() {
342 return KRADServiceLocatorWeb.getKualiModuleService();
343 }
344
345 protected DataDictionaryService getDataDictionaryService() {
346 return KRADServiceLocatorWeb.getDataDictionaryService();
347 }
348
349 protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
350 return KRADServiceLocatorWeb.getDataObjectAuthorizationService();
351 }
352
353 protected EncryptionService getEncryptionService() {
354 return CoreApiServiceLocator.getEncryptionService();
355 }
356
357 protected BusinessObjectService getBusinessObjectService() {
358 return KRADServiceLocator.getBusinessObjectService();
359 }
360
361 }