Coverage Report - org.kuali.rice.krad.workflow.service.impl.WorkflowAttributePropertyResolutionServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
WorkflowAttributePropertyResolutionServiceImpl
0%
0/163
0%
0/106
3.375
WorkflowAttributePropertyResolutionServiceImpl$RoutingAttributeTracker
0%
0/14
N/A
3.375
 
 1  
 /*
 2  
  * Copyright 2008-2009 The Kuali Foundation
 3  
  * 
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  * 
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  * 
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.krad.workflow.service.impl;
 17  
 
 18  
 
 19  
 import org.kuali.rice.core.api.util.type.KualiDecimal;
 20  
 import org.kuali.rice.kew.docsearch.SearchableAttributeDateTimeValue;
 21  
 import org.kuali.rice.kew.docsearch.SearchableAttributeFloatValue;
 22  
 import org.kuali.rice.kew.docsearch.SearchableAttributeLongValue;
 23  
 import org.kuali.rice.kew.docsearch.SearchableAttributeStringValue;
 24  
 import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
 25  
 import org.kuali.rice.kew.util.KEWConstants;
 26  
 import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
 27  
 import org.kuali.rice.kns.service.KNSServiceLocator;
 28  
 import org.kuali.rice.krad.bo.BusinessObject;
 29  
 import org.kuali.rice.krad.bo.PersistableBusinessObject;
 30  
 import org.kuali.rice.krad.datadictionary.DocumentCollectionPath;
 31  
 import org.kuali.rice.krad.datadictionary.DocumentValuePathGroup;
 32  
 import org.kuali.rice.krad.datadictionary.RoutingAttribute;
 33  
 import org.kuali.rice.krad.datadictionary.RoutingTypeDefinition;
 34  
 import org.kuali.rice.krad.datadictionary.SearchingTypeDefinition;
 35  
 import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
 36  
 import org.kuali.rice.krad.document.Document;
 37  
 import org.kuali.rice.krad.service.PersistenceStructureService;
 38  
 import org.kuali.rice.krad.util.DataTypeUtil;
 39  
 import org.kuali.rice.krad.util.ObjectUtils;
 40  
 import org.kuali.rice.krad.workflow.attribute.DataDictionarySearchableAttribute;
 41  
 import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
 42  
 
 43  
 import java.math.BigDecimal;
 44  
 import java.sql.Timestamp;
 45  
 import java.util.ArrayList;
 46  
 import java.util.Collection;
 47  
 import java.util.HashMap;
 48  
 import java.util.HashSet;
 49  
 import java.util.List;
 50  
 import java.util.Map;
 51  
 import java.util.Set;
 52  
 import java.util.Stack;
 53  
 
 54  
 /**
 55  
  * The default implementation of the WorkflowAttributePropertyResolutionServiceImpl
 56  
  */
 57  0
 public class WorkflowAttributePropertyResolutionServiceImpl implements WorkflowAttributePropertyResolutionService {
 58  
     private PersistenceStructureService persistenceStructureService;
 59  
     private BusinessObjectMetaDataService businessObjectMetaDataService;
 60  
 
 61  
     /**
 62  
      * Using the proper RoutingTypeDefinition for the current routing node of the document, aardvarks out the proper routing type qualifiers
 63  
      *
 64  
      */
 65  
     public List<Map<String, String>> resolveRoutingTypeQualifiers(Document document, RoutingTypeDefinition routingTypeDefinition) {
 66  0
         List<Map<String, String>> qualifiers = new ArrayList<Map<String, String>>();
 67  
         
 68  0
         if (routingTypeDefinition != null) {
 69  0
             document.populateDocumentForRouting();
 70  0
             RoutingAttributeTracker routingAttributeTracker = new RoutingAttributeTracker(routingTypeDefinition.getRoutingAttributes());
 71  0
             for (DocumentValuePathGroup documentValuePathGroup : routingTypeDefinition.getDocumentValuePathGroups()) {
 72  0
                 qualifiers.addAll(resolveDocumentValuePath(document, documentValuePathGroup, routingAttributeTracker));
 73  0
                 routingAttributeTracker.reset();
 74  
             }
 75  
         }
 76  0
         return qualifiers;
 77  
     }
 78  
     
 79  
     /**
 80  
      * Resolves all of the values in the given DocumentValuePathGroup from the given BusinessObject
 81  
      * @param businessObject the business object which is the source of values
 82  
      * @param group the DocumentValuePathGroup which tells us which values we want
 83  
      * @return a List of Map<String, String>s
 84  
      */
 85  
     protected List<Map<String, String>> resolveDocumentValuePath(BusinessObject businessObject, DocumentValuePathGroup group, RoutingAttributeTracker routingAttributeTracker) {
 86  
         List<Map<String, String>> qualifiers;
 87  0
         Map<String, String> qualifier = new HashMap<String, String>();
 88  0
         if (group.getDocumentValues() == null && group.getDocumentCollectionPath() == null) {
 89  0
             throw new IllegalStateException("A document value path group must have the documentValues property set, the documentCollectionPath property set, or both.");
 90  
         }
 91  0
         if (group.getDocumentValues() != null) {
 92  0
             addPathValuesToQualifier(businessObject, group.getDocumentValues(), routingAttributeTracker, qualifier);
 93  
         }
 94  0
         if (group.getDocumentCollectionPath() != null) {
 95  0
             qualifiers = resolveDocumentCollectionPath(businessObject, group.getDocumentCollectionPath(), routingAttributeTracker);
 96  0
             qualifiers = cleanCollectionQualifiers(qualifiers);
 97  0
             for (Map<String, String> collectionElementQualifier : qualifiers) {
 98  0
                 copyQualifications(qualifier, collectionElementQualifier);
 99  
             }
 100  
         } else {
 101  0
             qualifiers = new ArrayList<Map<String, String>>();
 102  0
             qualifiers.add(qualifier);
 103  
         }
 104  0
         return qualifiers;
 105  
     }
 106  
     
 107  
     /**
 108  
      * Resolves document values from a collection path on a given business object
 109  
      * @param businessObject the business object which has a collection, each element of which is a source of values
 110  
      * @param collectionPath the information about what values to pull from each element of the collection
 111  
      * @return a List of Map<String, String>s
 112  
      */
 113  
     protected List<Map<String, String>> resolveDocumentCollectionPath(BusinessObject businessObject, DocumentCollectionPath collectionPath, RoutingAttributeTracker routingAttributeTracker) {
 114  0
         List<Map<String, String>> qualifiers = new ArrayList<Map<String, String>>();
 115  0
         final Collection collectionByPath = getCollectionByPath(businessObject, collectionPath.getCollectionPath());
 116  0
         if (!ObjectUtils.isNull(collectionByPath)) {
 117  0
             if (collectionPath.getNestedCollection() != null) {
 118  
                 // we need to go through the collection...
 119  0
                 for (Object collectionElement : collectionByPath) {
 120  
                     // for each element, we need to get the child qualifiers
 121  0
                     if (collectionElement instanceof BusinessObject) {
 122  0
                         List<Map<String, String>> childQualifiers = resolveDocumentCollectionPath((BusinessObject)collectionElement, collectionPath.getNestedCollection(), routingAttributeTracker);
 123  0
                         for (Map<String, String> childQualifier : childQualifiers) {
 124  0
                             Map<String, String> qualifier = new HashMap<String, String>();
 125  0
                             routingAttributeTracker.checkPoint();
 126  
                             // now we need to get the values for the current element of the collection
 127  0
                             addPathValuesToQualifier(collectionElement, collectionPath.getDocumentValues(), routingAttributeTracker, qualifier);
 128  
                             // and move all the child keys to the qualifier
 129  0
                             copyQualifications(childQualifier, qualifier);
 130  0
                             qualifiers.add(qualifier);
 131  0
                             routingAttributeTracker.backUpToCheckPoint();
 132  0
                         }
 133  0
                     }
 134  
                 }
 135  
             } else {
 136  
                 // go through each element in the collection
 137  0
                 for (Object collectionElement : collectionByPath) {
 138  0
                     Map<String, String> qualifier = new HashMap<String, String>();
 139  0
                     routingAttributeTracker.checkPoint();
 140  0
                     addPathValuesToQualifier(collectionElement, collectionPath.getDocumentValues(), routingAttributeTracker, qualifier);
 141  0
                     qualifiers.add(qualifier);
 142  0
                     routingAttributeTracker.backUpToCheckPoint();
 143  0
                 }
 144  
             }
 145  
         }
 146  0
         return qualifiers;
 147  
     }
 148  
     
 149  
     /**
 150  
      * Returns a collection from a path on a business object
 151  
      * @param businessObject the business object to get values from
 152  
      * @param collectionPath the path to that collection
 153  
      * @return hopefully, a collection of objects
 154  
      */
 155  
     protected Collection getCollectionByPath(BusinessObject businessObject, String collectionPath) {
 156  0
         return (Collection)getPropertyByPath(businessObject, collectionPath.trim());
 157  
     }
 158  
     
 159  
     /**
 160  
      * Aardvarks values out of a business object and puts them into an Map<String, String>, based on a List of paths
 161  
      * @param businessObject the business object to get values from
 162  
      * @param paths the paths of values to get from the qualifier
 163  
      * @param routingAttributes the RoutingAttribute associated with this qualifier's document value
 164  
      * @param qualifier the qualifier to put values into
 165  
      */
 166  
     protected void addPathValuesToQualifier(Object businessObject, List<String> paths, RoutingAttributeTracker routingAttributes, Map<String, String> qualifier) {
 167  0
         if (ObjectUtils.isNotNull(paths)) {
 168  0
             for (String path : paths) {
 169  
                 // get the values for the paths of each element of the collection
 170  0
                 final Object value = getPropertyByPath(businessObject, path.trim());
 171  0
                 if (value != null) {
 172  0
                     qualifier.put(routingAttributes.getCurrentRoutingAttribute().getQualificationAttributeName(), value.toString());
 173  
                 }
 174  0
                 routingAttributes.moveToNext();
 175  0
             }
 176  
         }
 177  0
     }
 178  
     
 179  
     /**
 180  
      * Copies all the values from one qualifier to another
 181  
      * @param source the source of values
 182  
      * @param target the place to write all the values to
 183  
      */
 184  
     protected void copyQualifications(Map<String, String> source, Map<String, String> target) {
 185  0
         for (String key : source.keySet()) {
 186  0
             target.put(key, source.get(key));
 187  
         }
 188  0
     }
 189  
 
 190  
     /**
 191  
      * Resolves all of the searching values to index for the given document, returning a list of SearchableAttributeValue implementations
 192  
      *
 193  
      */
 194  
     public List<SearchableAttributeValue> resolveSearchableAttributeValues(Document document, WorkflowAttributes workflowAttributes) {
 195  0
         List<SearchableAttributeValue> valuesToIndex = new ArrayList<SearchableAttributeValue>();
 196  0
         if (workflowAttributes != null && workflowAttributes.getSearchingTypeDefinitions() != null) {
 197  0
             for (SearchingTypeDefinition definition : workflowAttributes.getSearchingTypeDefinitions()) {
 198  0
                 valuesToIndex.addAll(aardvarkValuesForSearchingTypeDefinition(document, definition));
 199  
             }
 200  
         }
 201  0
         return valuesToIndex;
 202  
     }
 203  
     
 204  
     /**
 205  
      * Pulls SearchableAttributeValue values from the given document for the given searchingTypeDefinition
 206  
      * @param document the document to get search values from
 207  
      * @param searchingTypeDefinition the current SearchingTypeDefinition to find values for
 208  
      * @return a List of SearchableAttributeValue implementations
 209  
      */
 210  
     protected List<SearchableAttributeValue> aardvarkValuesForSearchingTypeDefinition(Document document, SearchingTypeDefinition searchingTypeDefinition) {
 211  0
         List<SearchableAttributeValue> searchAttributes = new ArrayList<SearchableAttributeValue>();
 212  
         
 213  0
         final List<Object> searchValues = aardvarkSearchValuesForPaths(document, searchingTypeDefinition.getDocumentValues());
 214  0
         for (Object value : searchValues) {
 215  
             try {
 216  0
                 final SearchableAttributeValue searchableAttributeValue = buildSearchableAttribute(((Class<? extends BusinessObject>)Class.forName(searchingTypeDefinition.getSearchingAttribute().getBusinessObjectClassName())), searchingTypeDefinition.getSearchingAttribute().getAttributeName(), value);
 217  0
                 if (searchableAttributeValue != null) {
 218  0
                     searchAttributes.add(searchableAttributeValue);
 219  
                 }
 220  
             }
 221  0
             catch (ClassNotFoundException cnfe) {
 222  0
                 throw new RuntimeException("Could not find instance of class "+searchingTypeDefinition.getSearchingAttribute().getBusinessObjectClassName(), cnfe);
 223  0
             }
 224  
         }
 225  0
         return searchAttributes;
 226  
     }
 227  
     
 228  
     /**
 229  
      * Pulls values as objects from the document for the given paths
 230  
      * @param document the document to pull values from
 231  
      * @param paths the property paths to pull values
 232  
      * @return a List of values as Objects
 233  
      */
 234  
     protected List<Object> aardvarkSearchValuesForPaths(Document document, List<String> paths) {
 235  0
         List<Object> searchValues = new ArrayList<Object>();
 236  0
         for (String path : paths) {
 237  0
             flatAdd(searchValues, getPropertyByPath(document, path.trim()));
 238  
         }
 239  0
         return searchValues;
 240  
     }
 241  
     
 242  
     /**
 243  
      * Removes empty Map<String, String>s from the given List of qualifiers
 244  
      * @param qualifiers a List of Map<String, String>s holding qualifiers for responsibilities
 245  
      * @return a cleaned up list of qualifiers
 246  
      */
 247  
     protected List<Map<String, String>> cleanCollectionQualifiers(List<Map<String, String>> qualifiers) {
 248  0
        List<Map<String, String>> cleanedQualifiers = new ArrayList<Map<String, String>>();
 249  0
        for (Map<String, String> qualifier : qualifiers) {
 250  0
            if (qualifier.size() > 0) {
 251  0
                cleanedQualifiers.add(qualifier);
 252  
            }
 253  
        }
 254  0
        return cleanedQualifiers;
 255  
     }
 256  
 
 257  
     public String determineFieldDataType(Class<? extends BusinessObject> businessObjectClass, String attributeName) {
 258  0
         return DataTypeUtil.determineFieldDataType(businessObjectClass, attributeName);
 259  
     }
 260  
 
 261  
     /**
 262  
      * Using the type of the sent in value, determines what kind of SearchableAttributeValue implementation should be passed back 
 263  
      * @param attributeKey
 264  
      * @param value
 265  
      * @return
 266  
      */
 267  
     public SearchableAttributeValue buildSearchableAttribute(Class<? extends BusinessObject> businessObjectClass, String attributeKey, Object value) {
 268  0
         if (value == null) return null;
 269  0
         final String fieldDataType = determineFieldDataType(businessObjectClass, attributeKey);
 270  0
         if (fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_STRING)) return buildSearchableStringAttribute(attributeKey, value); // our most common case should go first
 271  0
         if (fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_FLOAT) && DataTypeUtil.isDecimaltastic(value.getClass())) return buildSearchableRealAttribute(attributeKey, value);
 272  0
         if (fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_DATE) && DataTypeUtil.isDateLike(value.getClass())) return buildSearchableDateTimeAttribute(attributeKey, value);
 273  0
         if (fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_LONG) && DataTypeUtil.isIntsy(value.getClass())) return buildSearchableFixnumAttribute(attributeKey, value);
 274  0
         if (fieldDataType.equals(DataDictionarySearchableAttribute.DATA_TYPE_BOOLEAN) && DataTypeUtil.isBooleanable(value.getClass())) return buildSearchableYesNoAttribute(attributeKey, value);
 275  0
         return buildSearchableStringAttribute(attributeKey, value);
 276  
     }
 277  
     
 278  
     /**
 279  
      * Builds a date time SearchableAttributeValue for the given key and value
 280  
      * @param attributeKey the key for the searchable attribute
 281  
      * @param value the value that will be coerced to date/time data
 282  
      * @return the generated SearchableAttributeDateTimeValue
 283  
      */
 284  
     protected SearchableAttributeDateTimeValue buildSearchableDateTimeAttribute(String attributeKey, Object value) {
 285  0
         SearchableAttributeDateTimeValue attribute = new SearchableAttributeDateTimeValue();
 286  0
         attribute.setSearchableAttributeKey(attributeKey);
 287  0
         attribute.setSearchableAttributeValue(new Timestamp(((java.util.Date)value).getTime()));
 288  0
         return attribute;
 289  
     }
 290  
     
 291  
     /**
 292  
      * Builds a "float" SearchableAttributeValue for the given key and value
 293  
      * @param attributeKey the key for the searchable attribute
 294  
      * @param value the value that will be coerced to "float" data
 295  
      * @return the generated SearchableAttributeFloatValue
 296  
      */
 297  
     protected SearchableAttributeFloatValue buildSearchableRealAttribute(String attributeKey, Object value) {
 298  0
         SearchableAttributeFloatValue attribute = new SearchableAttributeFloatValue();
 299  0
         attribute.setSearchableAttributeKey(attributeKey);
 300  0
         if (value instanceof BigDecimal) {
 301  0
             attribute.setSearchableAttributeValue((BigDecimal)value);
 302  0
         } else if (value instanceof KualiDecimal) {
 303  0
             attribute.setSearchableAttributeValue(((KualiDecimal)value).bigDecimalValue());
 304  
         } else {
 305  0
             attribute.setSearchableAttributeValue(new BigDecimal(((Number)value).doubleValue()));
 306  
         }
 307  0
         return attribute;
 308  
     }
 309  
     
 310  
     /**
 311  
      * Builds a "integer" SearchableAttributeValue for the given key and value
 312  
      * @param attributeKey the key for the searchable attribute
 313  
      * @param value the value that will be coerced to "integer" type data
 314  
      * @return the generated SearchableAttributeLongValue
 315  
      */
 316  
     protected SearchableAttributeLongValue buildSearchableFixnumAttribute(String attributeKey, Object value) {
 317  0
         SearchableAttributeLongValue attribute = new SearchableAttributeLongValue();
 318  0
         attribute.setSearchableAttributeKey(attributeKey);
 319  0
         attribute.setSearchableAttributeValue(new Long(((Number)value).longValue()));
 320  0
         return attribute;
 321  
     }
 322  
     
 323  
     /**
 324  
      * Our last ditch attempt, this builds a String SearchableAttributeValue for the given key and value
 325  
      * @param attributeKey the key for the searchable attribute
 326  
      * @param value the value that will be coerced to a String
 327  
      * @return the generated SearchableAttributeStringValue
 328  
      */
 329  
     protected SearchableAttributeStringValue buildSearchableStringAttribute(String attributeKey, Object value) {
 330  0
         SearchableAttributeStringValue attribute = new SearchableAttributeStringValue();
 331  0
         attribute.setSearchableAttributeKey(attributeKey);
 332  0
         attribute.setSearchableAttributeValue(value.toString());
 333  0
         return attribute;
 334  
     }
 335  
     
 336  
     /**
 337  
      * This builds a String SearchableAttributeValue for the given key and value, correctly correlating booleans
 338  
      * @param attributeKey the key for the searchable attribute
 339  
      * @param value the value that will be coerced to a String
 340  
      * @return the generated SearchableAttributeStringValue
 341  
      */
 342  
     protected SearchableAttributeStringValue buildSearchableYesNoAttribute(String attributeKey, Object value) {
 343  0
         SearchableAttributeStringValue attribute = new SearchableAttributeStringValue();
 344  0
         attribute.setSearchableAttributeKey(attributeKey);
 345  0
         final String boolValueAsString = booleanValueAsString((Boolean)value);
 346  0
         attribute.setSearchableAttributeValue(boolValueAsString);
 347  0
         return attribute;
 348  
     }
 349  
     
 350  
     /**
 351  
      * Converts the given boolean value to "" for null, "Y" for true, "N" for false
 352  
      * @param booleanValue the boolean value to convert
 353  
      * @return the corresponding String "Y","N", or ""
 354  
      */
 355  
     private String booleanValueAsString(Boolean booleanValue) {
 356  0
         if (booleanValue == null) return "";
 357  0
         if (booleanValue.booleanValue()) return "Y";
 358  0
         return "N";
 359  
     }
 360  
 
 361  
     public Object getPropertyByPath(Object object, String path) {
 362  0
         if (object instanceof Collection) return getPropertyOfCollectionByPath((Collection)object, path);
 363  
 
 364  0
         final String[] splitPath = headAndTailPath(path);
 365  0
         final String head = splitPath[0];
 366  0
         final String tail = splitPath[1];
 367  
         
 368  0
         if (object instanceof PersistableBusinessObject && tail != null) {
 369  0
             if (getBusinessObjectMetaDataService().getBusinessObjectRelationship((BusinessObject) object, head) != null) {
 370  0
                 ((PersistableBusinessObject)object).refreshReferenceObject(head);
 371  
 
 372  
             }
 373  
         }
 374  0
         final Object headValue = ObjectUtils.getPropertyValue(object, head);
 375  0
         if (!ObjectUtils.isNull(headValue)) {
 376  0
             if (tail == null) {
 377  0
                 return headValue;
 378  
             } else {
 379  
                 // we've still got path left...
 380  0
                 if (headValue instanceof Collection) {
 381  
                     // oh dear, a collection; we've got to loop through this
 382  0
                     Collection values = makeNewCollectionOfSameType((Collection)headValue);
 383  0
                     for (Object currentElement : (Collection)headValue) {
 384  0
                         flatAdd(values, getPropertyByPath(currentElement, tail));
 385  
                     }
 386  0
                     return values;
 387  
                 } else {
 388  0
                     return getPropertyByPath(headValue, tail);
 389  
                 }
 390  
             }
 391  
         }
 392  0
         return null;
 393  
     }
 394  
     
 395  
     /**
 396  
      * Finds a child object, specified by the given path, on each object of the given collection
 397  
      * @param collection the collection of objects
 398  
      * @param path the path of the property to retrieve
 399  
      * @return a Collection of the values culled from each child
 400  
      */
 401  
     public Collection getPropertyOfCollectionByPath(Collection collection, String path) {
 402  0
         Collection values = makeNewCollectionOfSameType(collection);
 403  0
         for (Object o : collection) {
 404  0
             flatAdd(values, getPropertyByPath(o, path));
 405  
         }
 406  0
         return values;
 407  
     }
 408  
     
 409  
     /**
 410  
      * Makes a new collection of exactly the same type of the collection that was handed to it
 411  
      * @param collection the collection to make a new collection of the same type as
 412  
      * @return a new collection.  Of the same type.
 413  
      */
 414  
     public Collection makeNewCollectionOfSameType(Collection collection) {
 415  0
         if (collection instanceof List) return new ArrayList();
 416  0
         if (collection instanceof Set) return new HashSet();
 417  
         try {
 418  0
             return collection.getClass().newInstance();
 419  
         }
 420  0
         catch (InstantiationException ie) {
 421  0
             throw new RuntimeException("Couldn't instantiate class of collection we'd already instantiated??", ie);
 422  
         }
 423  0
         catch (IllegalAccessException iae) {
 424  0
             throw new RuntimeException("Illegal Access on class of collection we'd already accessed??", iae);
 425  
         }
 426  
     }
 427  
     
 428  
     /**
 429  
      * Splits the first property off from a path, leaving the tail
 430  
      * @param path the path to split
 431  
      * @return an array; if the path is nested, the first element will be the first part of the path up to a "." and second element is the rest of the path while if the path is simple, returns the path as the first element and a null as the second element
 432  
      */
 433  
     protected String[] headAndTailPath(String path) {
 434  0
         final int firstDot = path.indexOf('.');
 435  0
         if (firstDot < 0) {
 436  0
             return new String[] { path, null };
 437  
         }
 438  0
         return new String[] { path.substring(0, firstDot), path.substring(firstDot + 1) };
 439  
     }
 440  
     
 441  
     /**
 442  
      * Convenience method which makes sure that if the given object is a collection, it is added to the given collection flatly
 443  
      * @param c a collection, ready to be added to
 444  
      * @param o an object of dubious type
 445  
      */
 446  
     protected void flatAdd(Collection c, Object o) {
 447  0
         if (o instanceof Collection) {
 448  0
             c.addAll((Collection) o);
 449  
         } else {
 450  0
             c.add(o);
 451  
         }
 452  0
     }
 453  
 
 454  
     /**
 455  
      * Gets the persistenceStructureService attribute. 
 456  
      * @return Returns the persistenceStructureService.
 457  
      */
 458  
     public PersistenceStructureService getPersistenceStructureService() {
 459  0
         return persistenceStructureService;
 460  
     }
 461  
 
 462  
     /**
 463  
      * Sets the persistenceStructureService attribute value.
 464  
      * @param persistenceStructureService The persistenceStructureService to set.
 465  
      */
 466  
     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
 467  0
         this.persistenceStructureService = persistenceStructureService;
 468  0
     }
 469  
     
 470  
     /**
 471  
      * Inner helper class which will track which routing attributes have been used
 472  
      */
 473  0
     class RoutingAttributeTracker {
 474  
         
 475  
         private List<RoutingAttribute> routingAttributes;
 476  
         private int currentRoutingAttributeIndex;
 477  
         private Stack<Integer> checkPoints;
 478  
         
 479  
         /**
 480  
          * Constructs a WorkflowAttributePropertyResolutionServiceImpl
 481  
          * @param routingAttributes the routing attributes to track
 482  
          */
 483  0
         public RoutingAttributeTracker(List<RoutingAttribute> routingAttributes) {
 484  0
             this.routingAttributes = routingAttributes;
 485  0
             checkPoints = new Stack<Integer>();
 486  0
         }
 487  
         
 488  
         /**
 489  
          * @return the routing attribute hopefully associated with the current qualifier
 490  
          */
 491  
         public RoutingAttribute getCurrentRoutingAttribute() {
 492  0
             return routingAttributes.get(currentRoutingAttributeIndex);
 493  
         }
 494  
         
 495  
         /**
 496  
          * Moves this routing attribute tracker to its next routing attribute
 497  
          */
 498  
         public void moveToNext() {
 499  0
             currentRoutingAttributeIndex += 1;
 500  0
         }
 501  
         
 502  
         /**
 503  
          * Check points at the current routing attribute, so that this position is saved
 504  
          */
 505  
         public void checkPoint() {
 506  0
             checkPoints.push(new Integer(currentRoutingAttributeIndex));
 507  0
         }
 508  
         
 509  
         /**
 510  
          * Returns to the point of the last check point
 511  
          */
 512  
         public void backUpToCheckPoint() {
 513  0
             currentRoutingAttributeIndex = checkPoints.pop().intValue();
 514  0
         }
 515  
         
 516  
         /**
 517  
          * Resets this RoutingAttributeTracker, setting the current RoutingAttribute back to the top one and
 518  
          * clearing the check point stack
 519  
          */
 520  
         public void reset() {
 521  0
             currentRoutingAttributeIndex = 0;
 522  0
             checkPoints.clear();
 523  0
         }
 524  
     }
 525  
 
 526  
     protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
 527  0
         if ( businessObjectMetaDataService == null ) {
 528  0
             businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
 529  
         }
 530  0
         return businessObjectMetaDataService;
 531  
     }
 532  
 }