001/*
002 * Copyright 2006 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.ole.gl.businessobject.inquiry;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023import java.util.Properties;
024
025import org.apache.commons.lang.StringUtils;
026import org.kuali.ole.gl.Constant;
027import org.kuali.ole.gl.businessobject.AccountBalance;
028import org.kuali.ole.gl.businessobject.lookup.BusinessObjectFieldConverter;
029import org.kuali.ole.sys.OLEConstants;
030import org.kuali.ole.sys.OLEPropertyConstants;
031import org.kuali.ole.sys.businessobject.inquiry.KfsInquirableImpl;
032import org.kuali.ole.sys.context.SpringContext;
033import org.kuali.rice.kew.api.doctype.DocumentType;
034import org.kuali.rice.kew.api.doctype.DocumentTypeService;
035import org.kuali.rice.kew.doctype.bo.DocumentTypeEBO;
036import org.kuali.rice.kns.lookup.HtmlData;
037import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData;
038import org.kuali.rice.kns.lookup.LookupUtils;
039import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
040import org.kuali.rice.kns.service.DataDictionaryService;
041import org.kuali.rice.krad.bo.BusinessObject;
042import org.kuali.rice.krad.datadictionary.AttributeDefinition;
043import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
044import org.kuali.rice.krad.service.PersistenceStructureService;
045import org.kuali.rice.krad.util.KRADConstants;
046import org.kuali.rice.krad.util.ObjectUtils;
047import org.kuali.rice.krad.util.UrlFactory;
048
049/**
050 * This class is the template class for the customized inqurable implementations used to generate balance inquiry screens.
051 */
052public abstract class AbstractGeneralLedgerInquirableImpl extends KfsInquirableImpl {
053    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AbstractGeneralLedgerInquirableImpl.class);
054    
055    private BusinessObject businessObject; 
056    
057    /**
058     * @see org.kuali.ole.sys.businessobject.inquiry.KfsInquirableImpl#getInquiryUrl(org.kuali.rice.krad.bo.BusinessObject, java.lang.String, boolean)
059     */
060    @Override
061    public HtmlData getInquiryUrl(BusinessObject businessObject, String attributeName, boolean forceInquiry) {
062        return this.getInquiryUrl(businessObject, attributeName);
063    }
064
065    /**
066     * Helper method to build an inquiry url for a result field.
067     * 
068     * @param businessObject the business object instance to build the urls for
069     * @param attributeName the attribute name which links to an inquirable
070     * @return String url to inquiry
071     */
072    public HtmlData getInquiryUrl(BusinessObject businessObject, String attributeName) {
073        this.setBusinessObject(businessObject);
074        AnchorHtmlData inquiryHref = new AnchorHtmlData(KRADConstants.EMPTY_STRING, KRADConstants.EMPTY_STRING);
075        BusinessObjectDictionaryService businessDictionary = SpringContext.getBean(BusinessObjectDictionaryService.class);
076        PersistenceStructureService persistenceStructureService = SpringContext.getBean(PersistenceStructureService.class);
077
078        String baseUrl = OLEConstants.INQUIRY_ACTION;
079        Properties parameters = new Properties();
080        parameters.put(OLEConstants.DISPATCH_REQUEST_PARAMETER, OLEConstants.START_METHOD);
081
082        Object attributeValue = null;
083        Class inquiryBusinessObjectClass = null;
084        String attributeRefName = "";
085        boolean isPkReference = false;
086
087        Map userDefinedAttributeMap = getUserDefinedAttributeMap();
088        boolean isUserDefinedAttribute = userDefinedAttributeMap == null ? false : userDefinedAttributeMap.containsKey(attributeName);
089
090        // determine the type of the given attribute: user-defined, regular, nested-referenced or primitive reference
091        if (isUserDefinedAttribute) {
092            attributeName = getAttributeName(attributeName);
093            inquiryBusinessObjectClass = getInquiryBusinessObjectClass(attributeName);
094            isPkReference = true;
095        }
096        else if (attributeName.equals(businessDictionary.getTitleAttribute(businessObject.getClass()))) {
097            inquiryBusinessObjectClass = businessObject.getClass();
098            isPkReference = true;
099        }
100        else if (ObjectUtils.isNestedAttribute(attributeName)) {
101            if (!"financialObject.financialObjectType.financialReportingSortCode".equals(attributeName)) {
102                inquiryBusinessObjectClass = LookupUtils.getNestedReferenceClass(businessObject, attributeName);
103            }
104            else {
105                return inquiryHref;
106            }
107        }
108        else {
109            Map primitiveReference = LookupUtils.getPrimitiveReference(businessObject, attributeName);
110            if (primitiveReference != null && !primitiveReference.isEmpty()) {
111                attributeRefName = (String) primitiveReference.keySet().iterator().next();
112                inquiryBusinessObjectClass = (Class) primitiveReference.get(attributeRefName);
113            }
114            attributeValue = ObjectUtils.getPropertyValue(businessObject, attributeName);
115            attributeValue = (attributeValue == null) ? "" : attributeValue.toString();
116        }
117
118        // process the business object class if the attribute name is not user-defined
119        if (!isUserDefinedAttribute) {
120            if (isExclusiveField(attributeName, attributeValue)) {
121                return inquiryHref;
122            }
123
124            if (inquiryBusinessObjectClass == null || businessDictionary.isInquirable(inquiryBusinessObjectClass) == null || !businessDictionary.isInquirable(inquiryBusinessObjectClass).booleanValue()) {
125                return inquiryHref;
126            }
127        }
128        parameters.put(OLEConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, inquiryBusinessObjectClass.getName());
129
130        List keys = new ArrayList();
131        Map<String,String> inquiryFields = new HashMap<String, String>();
132        if (isUserDefinedAttribute) {
133            baseUrl = getBaseUrl();
134            keys = buildUserDefinedAttributeKeyList();
135
136            parameters.put(OLEConstants.RETURN_LOCATION_PARAMETER, Constant.RETURN_LOCATION_VALUE);
137            parameters.put(OLEConstants.GL_BALANCE_INQUIRY_FLAG, "true");
138            parameters.put(OLEConstants.DISPATCH_REQUEST_PARAMETER, OLEConstants.SEARCH_METHOD);
139            parameters.put(OLEConstants.DOC_FORM_KEY, "88888888");
140
141            // add more customized parameters into the current parameter map
142            addMoreParameters(parameters, attributeName);
143        }
144        else if (persistenceStructureService.isPersistable(inquiryBusinessObjectClass)) {
145            keys = persistenceStructureService.listPrimaryKeyFieldNames(inquiryBusinessObjectClass);
146        }
147
148        // build key value url parameters used to retrieve the business object
149        if (keys != null) {
150            for (Iterator keyIterator = keys.iterator(); keyIterator.hasNext();) {
151                String keyName = (String) keyIterator.next();
152
153                // convert the key names based on their formats and types
154                String keyConversion = keyName;
155                if (ObjectUtils.isNestedAttribute(attributeName)) {
156                    if (isUserDefinedAttribute) {
157                        keyConversion = keyName;
158                    }
159                    else {
160                        keyConversion = ObjectUtils.getNestedAttributePrefix(attributeName) + "." + keyName;
161                    }
162                }
163                else {
164                    if (isPkReference) {
165                        keyConversion = keyName;
166                    }
167                    else {
168                        keyConversion = persistenceStructureService.getForeignKeyFieldName(businessObject.getClass(), attributeRefName, keyName);
169                    }
170                }
171
172                Object keyValue = ObjectUtils.getPropertyValue(businessObject, keyConversion);
173                keyValue = (keyValue == null) ? "" : keyValue.toString();
174
175                // convert the key value and name into the given ones
176                Object tempKeyValue = this.getKeyValue(keyName, keyValue);
177                keyValue = tempKeyValue == null ? keyValue : tempKeyValue;
178
179                String tempKeyName = this.getKeyName(keyName);
180                keyName = tempKeyName == null ? keyName : tempKeyName;
181
182                // add the key-value pair into the parameter map
183                if (keyName != null){
184                    parameters.put(keyName, keyValue);
185                    inquiryFields.put(keyName, keyValue.toString());
186                }
187            }
188        }
189
190        // Hack to make this work. I don't know why it doesn't pick up the whole primary key for these. The last big change to
191        // KualiInquirableImpl
192        // broke all of this
193        if (businessObject instanceof AccountBalance) {
194            AccountBalance ab = (AccountBalance) businessObject;
195            if ("financialObject.financialObjectLevel.financialConsolidationObject.finConsolidationObjectCode".equals(attributeName)) {
196                parameters.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, ab.getChartOfAccountsCode());
197            }
198            else if ("financialObject.financialObjectLevel.financialObjectLevelCode".equals(attributeName)) {
199                parameters.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, ab.getChartOfAccountsCode());
200            }
201        }
202
203        return getHyperLink(inquiryBusinessObjectClass, inquiryFields, UrlFactory.parameterizeUrl(baseUrl, parameters));
204    }
205
206    /**
207     * This method builds the inquiry url for user-defined attribute
208     * 
209     * @return a List of attribute keys for the inquiry url
210     */
211    protected abstract List buildUserDefinedAttributeKeyList();
212
213    /**
214     * This method defines the user-defined attribute map
215     * 
216     * @return the user-defined attribute map
217     */
218    protected abstract Map getUserDefinedAttributeMap();
219
220    /**
221     * This method finds the matching attribute name of given one
222     * 
223     * @param attributeName the given attribute name
224     * @return the attribute name from the given one
225     */
226    protected abstract String getAttributeName(String attributeName);
227
228    /**
229     * This method finds the matching the key value of the given one
230     * 
231     * @param keyName the given key name
232     * @param keyValue the given key value
233     * @return the key value from the given key value
234     */
235    protected abstract Object getKeyValue(String keyName, Object keyValue);
236
237    /**
238     * This method finds the matching the key name of the given one
239     * 
240     * @param keyName the given key name
241     * @return the key value from the given key name
242     */
243    protected abstract String getKeyName(String keyName);
244
245    /**
246     * This method defines the lookupable implementation attribute name
247     * 
248     * @return the lookupable implementation attribute name
249     */
250    protected abstract String getLookupableImplAttributeName();
251
252    /**
253     * This method defines the base inquiry url
254     * 
255     * @return the base inquiry url
256     */
257    protected abstract String getBaseUrl();
258
259    /**
260     * This method gets the class name of the inquiry business object for a given attribute.
261     * 
262     * @return the class name of the inquiry business object for a given attribute
263     */
264    protected abstract Class getInquiryBusinessObjectClass(String attributeName);
265
266    /**
267     * This method adds more parameters into the curren parameter map
268     * 
269     * @param parameter the current parameter map
270     */
271    protected abstract void addMoreParameters(Properties parameter, String attributeName);
272
273    /**
274     * This method determines whether the input name-value pair is exclusive from the processing
275     * 
276     * @param keyName the name of the name-value pair
277     * @param keyValue the value of the name-value pair
278     * @return true if the input key is in the exclusive list; otherwise, false
279     */
280    protected boolean isExclusiveField(Object keyName, Object keyValue) {
281
282        if (keyName != null && keyValue != null) {
283            String convertedKeyName = BusinessObjectFieldConverter.convertFromTransactionPropertyName(keyName.toString());
284
285            if (convertedKeyName.equals(OLEPropertyConstants.SUB_ACCOUNT_NUMBER) && keyValue.equals(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER)) {
286                return true;
287            }
288            else if (convertedKeyName.equals(OLEPropertyConstants.SUB_OBJECT_CODE) && keyValue.equals(Constant.CONSOLIDATED_SUB_OBJECT_CODE)) {
289                return true;
290            }
291            else if (convertedKeyName.equals(OLEPropertyConstants.OBJECT_TYPE_CODE) && keyValue.equals(Constant.CONSOLIDATED_OBJECT_TYPE_CODE)) {
292                return true;
293            }
294            if (convertedKeyName.equals(OLEPropertyConstants.SUB_ACCOUNT_NUMBER) && keyValue.equals(OLEConstants.getDashSubAccountNumber())) {
295                return true;
296            }
297            else if (convertedKeyName.equals(OLEPropertyConstants.SUB_OBJECT_CODE) && keyValue.equals(OLEConstants.getDashFinancialSubObjectCode())) {
298                return true;
299            }
300            else if (convertedKeyName.equals(OLEPropertyConstants.PROJECT_CODE) && keyValue.equals(OLEConstants.getDashProjectCode())) {
301                return true;
302            }
303        }
304        return false;
305    }
306
307    /**
308     * This method recovers the values of the given keys
309     * 
310     * @param fieldValues unconsolidated values
311     * @param keyName a key name that may be in the fieldValues map
312     * @param keyValue a key value that may be in the fieldValues map
313     * @return the original value for a previously consolidated value
314     */
315    protected String recoverFieldValueFromConsolidation(Map fieldValues, Object keyName, Object keyValue) {
316        if (fieldValues == null || keyName == null || keyValue == null) {
317            return Constant.EMPTY_STRING;
318        }
319
320        Map convertedFieldValues = BusinessObjectFieldConverter.convertFromTransactionFieldValues(fieldValues);
321        String convertedKeyName = BusinessObjectFieldConverter.convertFromTransactionPropertyName(keyName.toString());
322
323        if (convertedKeyName.equals(OLEPropertyConstants.SUB_ACCOUNT_NUMBER) && keyValue.equals(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER)) {
324            return this.getValueFromFieldValues(convertedFieldValues, keyName);
325        }
326        else if (convertedKeyName.equals(OLEPropertyConstants.SUB_OBJECT_CODE) && keyValue.equals(Constant.CONSOLIDATED_SUB_OBJECT_CODE)) {
327            return this.getValueFromFieldValues(convertedFieldValues, keyName);
328        }
329        else if (convertedKeyName.equals(OLEPropertyConstants.OBJECT_TYPE_CODE) && keyValue.equals(Constant.CONSOLIDATED_OBJECT_TYPE_CODE)) {
330            return this.getValueFromFieldValues(convertedFieldValues, keyName);
331        }
332
333        return Constant.EMPTY_STRING;
334    }
335
336    /**
337     * Utility method to get the value of the given key from the field values
338     * 
339     * @param fieldValues a Map of key values
340     * @param keyName the name of the key to retrieve the value from
341     * @return the value for the key, or, if not found, an empty String
342     */
343    private String getValueFromFieldValues(Map fieldValues, Object keyName) {
344        String keyValue = Constant.EMPTY_STRING;
345
346        if (fieldValues.containsKey(keyName)) {
347            keyValue = (String) fieldValues.get(keyName);
348        }
349        return keyValue;
350    }
351
352    /**
353     * This takes a map of field values and then returns it without processing it, making this a sort
354     * of identity method for Maps
355     * 
356     * @param fieldValues field values to return to the user
357     * @return the Map you sent in as a parameter
358     */
359    public Map getFieldValues(Map fieldValues) {
360        return fieldValues;
361    }
362
363    /**
364     * Given the nested name of an attribute in an object, returns the class that attribute will return
365     * 
366     * @param businessObject the business object to find the propery class for
367     * @param attributeName the nested name of the attribute to find the class for
368     * @return the class of the nested attribute
369     */
370    public Class getNestedInquiryBusinessObjectClass(BusinessObject businessObject, String attributeName) {
371        // TODO: not finished
372        Class inquiryBusinessObjectClass = null;
373        String entryName = businessObject.getClass().getName();
374        if (LOG.isDebugEnabled()) {
375            LOG.debug("businessObject: " + entryName);
376            LOG.debug("attributeName: " + attributeName);    
377        }
378        DataDictionaryService dataDictionary = SpringContext.getBean(DataDictionaryService.class);
379        AttributeDefinition attributeDefinition = null;
380
381        if (StringUtils.isBlank(attributeName)) {
382            throw new IllegalArgumentException("invalid (blank) attributeName");
383        }
384
385        DataDictionaryEntryBase entry = (DataDictionaryEntryBase) dataDictionary.getDataDictionary().getDictionaryObjectEntry(entryName);
386        if (entry != null) {
387            attributeDefinition = entry.getAttributeDefinition(attributeName);
388            inquiryBusinessObjectClass = LookupUtils.getNestedReferenceClass(businessObject, attributeName);
389        }
390
391        // TODO: need to get class for which this property belongs
392//        if (attributeDefinition instanceof AttributeReferenceDefinition) {
393//            AttributeReferenceDefinition attributeReferenceDefinition = (AttributeReferenceDefinition) attributeDefinition;
394//            LOG.debug("Source Classname = " + attributeReferenceDefinition.getSourceClassName());
395//            LOG.debug("Source Attribute = " + attributeReferenceDefinition.getSourceAttributeName());
396//
397//            try {
398//                inquiryBusinessObjectClass = Class.forName(attributeReferenceDefinition.getSourceClassName());
399//            }
400//            catch (Exception e) {
401//                throw new IllegalArgumentException("fail to construct a Class");
402//            }
403//        }
404
405        return inquiryBusinessObjectClass;
406    }
407    
408    /**
409     * Builds URL to document type inquiry based on a given document type code
410     * 
411     * @param docTypeCode - document type code to inquiry on
412     * @return {@link HtmlData} representing inquiry URL
413     */
414    protected HtmlData getDocTypeInquiryUrl(String docTypeCode) {
415        DocumentType docTypeDTO = SpringContext.getBean(DocumentTypeService.class).getDocumentTypeByName(docTypeCode);
416        if ( docTypeDTO == null ) {
417            return new AnchorHtmlData();
418        }
419        String baseUrl = OLEConstants.INQUIRY_ACTION;
420
421        Properties parameters = new Properties();
422        parameters.put(OLEConstants.DISPATCH_REQUEST_PARAMETER, OLEConstants.START_METHOD);
423        parameters.put(OLEConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, DocumentTypeEBO.class.getName());
424        parameters.put(OLEConstants.DOC_FORM_KEY, "88888888");
425        parameters.put(OLEPropertyConstants.DOCUMENT_TYPE_ID, docTypeDTO.getId().toString());
426
427        Map<String, String> inquiryFields = new HashMap<String, String>();
428        inquiryFields.put(OLEPropertyConstants.DOCUMENT_TYPE_ID, docTypeDTO.getId().toString());
429
430        return getHyperLink(DocumentTypeEBO.class, inquiryFields, UrlFactory.parameterizeUrl(baseUrl, parameters));
431    }
432
433    /**
434     * Gets the businessObject attribute. 
435     * @return Returns the businessObject.
436     */
437    public BusinessObject getBusinessObject() {
438        return businessObject;
439    }
440
441    /**
442     * Sets the businessObject attribute value.
443     * @param businessObject The businessObject to set.
444     */
445    public void setBusinessObject(BusinessObject businessObject) {
446        this.businessObject = businessObject;
447    }
448}