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