org.amplafi.flow.flowproperty.FlowPropertyDefinitionImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.amplafi.flow.flowproperty.FlowPropertyDefinitionImpl.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied. See the License for
 * the specific language governing permissions and limitations under the
 * License.
 */

package org.amplafi.flow.flowproperty;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import static com.sworddance.util.CUtilities.*;

import org.amplafi.flow.DataClassDefinition;
import org.amplafi.flow.Flow;
import org.amplafi.flow.FlowActivity;
import org.amplafi.flow.FlowActivityPhase;
import org.amplafi.flow.FlowConfigurationException;
import org.amplafi.flow.FlowException;
import org.amplafi.flow.FlowExecutionException;
import org.amplafi.flow.FlowImplementor;
import org.amplafi.flow.FlowPropertyDefinition;
import org.amplafi.flow.FlowPropertyExpectation;
import org.amplafi.flow.FlowPropertyValueProvider;
import org.amplafi.flow.FlowState;
import org.amplafi.flow.FlowStepDirection;
import org.amplafi.flow.translator.FlowTranslator;

import com.sworddance.util.NotNullIterator;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.builder.EqualsBuilder;

import static org.apache.commons.lang.StringUtils.*;

/**
 * Defines a property that will be assigned as part of a {@link Flow} or
 * {@link FlowActivity}. This allows the value to be available to the component
 * or page referenced by a {@link FlowActivity}.
 *
 * @author Patrick Moore
 */
public class FlowPropertyDefinitionImpl implements FlowPropertyDefinitionImplementor {

    /**
     * Name of the property as used in the flow code.
     */
    private String name;

    /**
     * when a flow starts this should be the initial value set unless there is
     * already a value.
     * This is different than the idea of "defaultObject" because the 'initial' check-and-set only happens when the flow is initializing.
     * whereas the 'defaultObject" happens every time there is null for a value.
     *
     * Initial is stored in the FlowState map and defaultObject is not.
     */
    private String initial;

    // TODO: if a list of providers is defined then it becomes impossible to determine the minimum set of dependencies.
    // dependencies required by alternative FPVP may never be needed because the FPVP is never called.
    private transient FlowPropertyValueProvider<FlowPropertyProvider> flowPropertyValueProvider;
    private transient FlowPropertyValuePersister<FlowPropertyProvider> flowPropertyValuePersister;
    private transient List<FlowPropertyValueChangeListener> flowPropertyValueChangeListeners = new CopyOnWriteArrayList<FlowPropertyValueChangeListener>();

    /**
     * on {@link FlowActivity#passivate(boolean, FlowStepDirection)} the object should be saved
     * back. This allows complex object to have their string representation
     * saved. Necessary for cases when setProperty() is not used to save value
     * changes, because the object stored in the property is being modified not
     * the usual case of where a new object is saved.
     *
     * Use case: Any collections. Maybe collection properties should automatically have saveBack set?
     */
    private Boolean saveBack;

    /**
     * if the property does not exist in the cache then create a new instance.
     * As a result, the property will never return a null.
     */
    private Boolean autoCreate;

    private DataClassDefinition dataClassDefinition;

    private Set<String> alternates;

    /**
     * data that should not be outputted or saved. for example, passwords.
     */
    private ExternalPropertyAccessRestriction externalPropertyAccessRestriction;

    private FlowActivityPhase flowActivityPhase;
    private PropertyUsage propertyUsage;
    private PropertyScope propertyScope;

    /**
     * A set of {@link FlowPropertyExpectation}s that this property definition needs. Note that a {@link FlowPropertyExpectation} can be optional.
     */
    private Set<FlowPropertyExpectation> propertiesDependentOn;

    protected FlowPropertyDefinitionImpl(FlowPropertyExpectation clone) {
        // TODO : validate there is a name
        this.name = clone.getName();
        if (isBlank(this.name)) {
            throw new FlowConfigurationException("Property name cannot be null or blank");
        }
        // TODO : validate there are data types (but need to allow for the String.class being the data type)
        this.dataClassDefinition = (DataClassDefinition) clone.getDataClassDefinition().clone();
        if (isNotEmpty(clone.getAlternates())) {
            this.alternates = new HashSet<String>(clone.getAlternates());
        }
        this.autoCreate = clone.getAutoCreate();
        this.flowPropertyValueProvider = clone.getFlowPropertyValueProvider();
        this.flowPropertyValuePersister = clone.getFlowPropertyValuePersister();
        if (clone.getInitial() != null) {
            this.initial = clone.getInitial();
        }
        if (clone.getExternalPropertyAccessRestriction() != null) {
            this.externalPropertyAccessRestriction = clone.getExternalPropertyAccessRestriction();
        }
        this.saveBack = clone.getSaveBack();
        this.flowActivityPhase = clone.getPropertyRequired();
        this.propertyUsage = clone.getPropertyUsage();
        this.propertyScope = clone.getPropertyScope();
        this.propertiesDependentOn = clone.getPropertiesDependentOn();
    }

    /**
     *
     * @param flowPropertyProvider
     * @return defaultObject Should not save default object in
     *         {@link FlowPropertyDefinitionImpl} if it is mutable.
     */
    @Override
    public Object getDefaultObject(FlowPropertyProvider flowPropertyProvider) {
        Object value = null;
        FlowPropertyValueProvider<? extends FlowPropertyProvider> propertyValueProvider = this.flowPropertyValueProvider;
        if (propertyValueProvider != null) {
            // 6 may 2012 - PATM - I forgot exact reason for this check. I believe it was for case where 2 different FlowActivities defined
            // property with same name. This was important for proper interpretation of the serialized property.
            // this code predated to a large extend the generic global use of FlowPropertyValueProviders. (it may no longer be useful).
            // problem is that if a flow activity defined a property and then the property was promoted to be a flow wide property, (it appears)
            // that the only the original flowactivity could get the default.
            Class<? extends FlowPropertyProvider> expected = propertyValueProvider.getFlowPropertyProviderClass();
            if (!(expected == null || expected.isAssignableFrom(flowPropertyProvider.getClass()))) {
                throw new FlowExecutionException(this, ": expected a ", expected, " but got a ",
                        flowPropertyProvider.getClass());
            }
            try {
                // HACK : casting. fix definition later.
                value = ((FlowPropertyValueProvider<FlowPropertyProvider>) propertyValueProvider)
                        .get(flowPropertyProvider, this);
            } catch (FlowException e) {
                throw e;
            } catch (Exception e) {
                throw new FlowExecutionException(
                        this.getName() + ": PropertyValueProvider threw an exception. propertyValueProvider="
                                + propertyValueProvider,
                        e);
            }
        } else {
            // TODO -- may still want to call this if flowPropertyValueProvider returns null.
            // for example the property type is a primitive.
            value = this.getDataClassDefinition().getFlowTranslator().getDefaultObject(flowPropertyProvider);
        }
        // TODO -- do we want to set the default object? or recalculate it each time?
        // might be important if the default object is to get modified or if a FPD is shared.
        return value;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getMapKey() {
        return getName();
    }

    @Override
    public FlowTranslator<?> getTranslator() {
        return this.getDataClassDefinition().getFlowTranslator();
    }

    @Override
    public boolean isFlowTranslatorSet() {
        return getDataClassDefinition().isFlowTranslatorSet();
    }

    @Override
    public String getInitial() {
        return this.initial;
    }

    @Override
    public Boolean getAutoCreate() {
        return this.autoCreate;
    }

    @Override
    public boolean isAutoCreate() {
        if (isDefaultAvailable()) {
            return true;
        }
        return isDefaultByClassAvailable();
    }

    /**
     * @return
     */
    private boolean isDefaultByClassAvailable() {
        if (this.autoCreate != null) {
            return getBoolean(this.autoCreate);
        }
        if (!getDataClassDefinition().isCollection()) {
            Class<?> dataClass = getDataClassDefinition().getDataClass();
            if (dataClass.isPrimitive()) {
                return true;
            } else if (dataClass.isInterface() || dataClass.isEnum()) {
                return false;
            }
        }
        return false;
    }

    @Override
    public boolean isDefaultAvailable() {
        return this.flowPropertyValueProvider != null;
    }

    @Override
    public String toString() {
        return getName() + "(scope=" + getPropertyScope() + ", usage=" + getPropertyUsage() + ") :"
                + this.dataClassDefinition;
    }

    @Override
    public <T> String serialize(T object) {
        if (this.getDataClassDefinition().getFlowTranslator() == null) {
            return null;
        } else {
            try {
                Object serialized = this.getDataClassDefinition().serialize(this, object);
                return ObjectUtils.toString(serialized, null);
            } catch (FlowPropertySerializationNotPossibleException e) {
                return null;
            }
        }
    }

    @Override
    public <T, W> W serialize(W outputWriter, T value) {
        try {
            return this.dataClassDefinition.serialize(this, outputWriter, value);
        } catch (FlowPropertySerializationNotPossibleException e) {
            return outputWriter;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <V> V deserialize(FlowPropertyProvider flowPropertyProvider, Object serializedObject)
            throws FlowException {
        return (V) this.getDataClassDefinition().deserialize(flowPropertyProvider, this, serializedObject);
    }

    /**
     * @return the propertyRequired
     */
    @Override
    public FlowActivityPhase getPropertyRequired() {
        return this.flowActivityPhase == null ? FlowActivityPhase.optional : this.flowActivityPhase;
    }

    /**
     * @return the propertyUsage (default {@link PropertyUsage#consume}
     */
    @Override
    public PropertyUsage getPropertyUsage() {
        if (isPropertyUsageSet()) {
            return this.propertyUsage;
        } else {
            return this.getPropertyScope().getDefaultPropertyUsage();
        }
    }

    /**
     * @return true if the propertyUsage is explicitly set.
     */
    @Override
    public boolean isPropertyUsageSet() {
        return this.propertyUsage != null;
    }

    /**
     * @return the propertyScope
     */
    @Override
    public PropertyScope getPropertyScope() {
        return isPropertyScopeSet() ? this.propertyScope : PropertyScope.flowLocal;
    }

    @Override
    public boolean isPropertyScopeSet() {
        return this.propertyScope != null;
    }

    // HACK -- to fix later...
    @Override
    public FlowPropertyDefinition initialize() {
        checkInitial(this.getInitial());
        return this;
    }

    /**
     * make sure the initial value can be deserialized to the expected type.
     */
    private void checkInitial(String value) {
        if (!this.getDataClassDefinition().isDeserializable(this, value)) {
            throw new FlowException(this + " while checking initial value=" + value);
        }
    }

    /**
     * @return the dataClassDefinition
     */
    @Override
    public DataClassDefinition getDataClassDefinition() {
        return this.dataClassDefinition;
    }

    @Override
    public Class<? extends Object> getDataClass() {
        return getDataClassDefinition().getDataClass();
    }

    @Override
    public Set<String> getAlternates() {
        return this.alternates;
    }

    @Override
    public Set<String> getAllNames() {
        Set<String> allNames = new LinkedHashSet<String>();
        allNames.add(this.getName());
        if (this.alternates != null) {
            allNames.addAll(getAlternates());
        }
        return allNames;
    }

    @Override
    public ExternalPropertyAccessRestriction getExternalPropertyAccessRestriction() {
        if (this.externalPropertyAccessRestriction != null) {
            return this.externalPropertyAccessRestriction;
        } else {
            return getPropertyUsage() == PropertyUsage.internalState ? ExternalPropertyAccessRestriction.noAccess
                    : ExternalPropertyAccessRestriction.noRestrictions;
        }
    }

    @Override
    public boolean isExportable() {
        return this.getExternalPropertyAccessRestriction().isExternalReadAccessAllowed();
    }

    public boolean isSensitive() {
        return !getExternalPropertyAccessRestriction().isExternalReadAccessAllowed();
    }

    @Override
    public boolean isAssignableFrom(Class<?> clazz) {
        return this.getDataClassDefinition().isAssignableFrom(clazz);
    }

    @Override
    public boolean isDataClassMergeable(FlowPropertyDefinition flowPropertyDefinition) {
        return getDataClassDefinition()
                .isMergable(((FlowPropertyDefinitionImplementor) flowPropertyDefinition).getDataClassDefinition());
    }

    @Override
    public boolean isMergeable(FlowPropertyDefinition property) {
        if (!(property instanceof FlowPropertyDefinitionImplementor)) {
            return false;
        }
        FlowPropertyDefinitionImplementor source = (FlowPropertyDefinitionImplementor) property;
        boolean result = isDataClassMergeable(source);
        if (result) {
            result &= this.flowPropertyValueProvider == null || source.getFlowPropertyValueProvider() == null
                    || this.flowPropertyValueProvider.equals(source.getFlowPropertyValueProvider());
            if (result) {
                result &= !isPropertyScopeSet() || !property.isPropertyScopeSet()
                        || getPropertyScope() == property.getPropertyScope();
                result &= !isPropertyUsageSet() || !property.isPropertyUsageSet()
                        || getPropertyUsage().isChangeableTo(property.getPropertyUsage())
                        || property.getPropertyUsage().isChangeableTo(getPropertyUsage());
                // TODO: ExternalPropertyAccessRestriction
                if (!result) {
                    //                    System.out.println("scope clash");
                }
            } else {
                //                System.out.println("provider clash");
            }
        } else {
            //            System.out.println("dataclass clash");
        }
        return result;
    }

    /**
     * Copy from property any fields not already set.
     *
     * TODO: Need to check {@link PropertyScope}!! How to handle activityLocal/flowLocals???
     * @param property
     * @return true if there is no conflict in the dataClass, true if
     *         this.dataClass cannot be assigned by previous.dataClass
     *         instances.
     */
    @Override
    public boolean merge(FlowPropertyDefinition property) {
        if (property == null || this == property) {
            return true;
        } else if (!isDataClassMergeable(property)) {
            return false;
        }
        FlowPropertyDefinitionImpl source = (FlowPropertyDefinitionImpl) property;
        boolean noMergeConflict = isMergeable(source);
        if (this.autoCreate == null && source.autoCreate != null) {
            this.autoCreate = source.autoCreate;
        }
        this.getDataClassDefinition().merge(source.dataClassDefinition);

        if (isNotEmpty(source.alternates)) {
            if (this.alternates == null) {
                this.alternates = new HashSet<String>(source.alternates);
            } else {
                this.alternates.addAll(source.alternates);
            }
        }

        if (this.flowPropertyValueProvider == null && source.flowPropertyValueProvider != null) {
            this.flowPropertyValueProvider = source.flowPropertyValueProvider;
        }
        if (this.flowPropertyValuePersister == null && source.flowPropertyValuePersister != null) {
            this.flowPropertyValuePersister = source.flowPropertyValuePersister;
        }
        // TODO: merging should handling chained notification.
        if (this.flowPropertyValueChangeListeners == null && source.flowPropertyValueChangeListeners != null) {
            this.flowPropertyValueChangeListeners = source.flowPropertyValueChangeListeners;
        }
        if (this.initial == null && source.initial != null) {
            this.initial = source.initial;
            checkInitial(this.initial);
        }
        if (this.saveBack == null && source.saveBack != null) {
            this.saveBack = source.saveBack;
        }
        if (this.externalPropertyAccessRestriction == null && source.externalPropertyAccessRestriction != null) {
            this.externalPropertyAccessRestriction = source.externalPropertyAccessRestriction;
        }
        if (!isPropertyScopeSet() && source.isPropertyScopeSet()) {
            this.propertyScope = source.propertyScope;
        }
        if (source.isPropertyUsageSet()) {
            this.propertyUsage = PropertyUsage.survivingPropertyUsage(this.propertyUsage, source.propertyUsage);
        }

        // TODO : determine how to handle propertyRequired / PropertyUsage/PropertyScope/ExternalPropertyAccessRestriction which vary between different FAs in the same Flow.
        return noMergeConflict;
    }

    @Override
    public Boolean getSaveBack() {
        return this.saveBack;
    }

    @Override
    public boolean isSaveBack() {
        return getBoolean(this.saveBack);
    }

    @Override
    public boolean isCopyBackOnFlowSuccess() {
        // TODO: This needs to be a more complex test involving ExternalPropertyAccessRestriction as well.
        return getPropertyUsage().isOutputedProperty();
    }

    private boolean getBoolean(Boolean b) {
        return b != null && b;
    }

    @Override
    public boolean isCacheOnly() {
        return getPropertyScope().isCacheOnly();
    }

    @Override
    public boolean isReadOnly() {
        return getPropertyUsage().getAltersProperty() == Boolean.FALSE;
    }

    /**
     * @param <FA>
     * @param flowPropertyValueProvider the flowPropertyValueProvider to set
     */
    @Override
    @SuppressWarnings("unchecked")
    public <FA extends FlowPropertyProvider> void setFlowPropertyValueProvider(
            FlowPropertyValueProvider<FA> flowPropertyValueProvider) {
        // TODO: lock down for templates
        this.flowPropertyValueProvider = (FlowPropertyValueProvider<FlowPropertyProvider>) flowPropertyValueProvider;
    }

    /**
     * @param <FA>
     * @return the flowPropertyValueProvider
     */
    @Override
    @SuppressWarnings("unchecked")
    public <FA extends FlowPropertyProvider> FlowPropertyValueProvider<FA> getFlowPropertyValueProvider() {
        return (FlowPropertyValueProvider<FA>) this.flowPropertyValueProvider;
    }

    /**
     * @return the flowPropertyValuePersister
     */
    @Override
    public FlowPropertyValuePersister<FlowPropertyProvider> getFlowPropertyValuePersister() {
        return this.flowPropertyValuePersister;
    }

    @Override
    public List<Object> getObjectsNeedingToBeWired() {
        List<Object> objectsNeedingToBeWired = new ArrayList<Object>();
        addAllNotNull(objectsNeedingToBeWired, getFlowPropertyValueChangeListeners());
        addAllNotNull(objectsNeedingToBeWired, getTranslator(), getElementFlowTranslator(), getKeyFlowTranslator(),
                getFlowPropertyValuePersister(), getFlowPropertyValueProvider());
        return objectsNeedingToBeWired;
    }

    private FlowTranslator getKeyFlowTranslator() {
        DataClassDefinition keyDataClassDefinition = getDataClassDefinition().getKeyDataClassDefinition();
        return keyDataClassDefinition != null ? keyDataClassDefinition.getFlowTranslator() : null;
    }

    private FlowTranslator getElementFlowTranslator() {
        DataClassDefinition elementDataClassDefinition = getDataClassDefinition().getElementDataClassDefinition();
        return elementDataClassDefinition != null ? elementDataClassDefinition.getFlowTranslator() : null;
    }

    /**
     * @return the flowPropertyValueChangeListener
     */
    @Override
    public List<FlowPropertyValueChangeListener> getFlowPropertyValueChangeListeners() {
        return this.flowPropertyValueChangeListeners;
    }

    /**
     * @return the propertiesDependentOn
     */
    @Override
    public Set<FlowPropertyExpectation> getPropertiesDependentOn() {
        return this.propertiesDependentOn;
    }

    @Override
    public boolean isDynamic() {
        if (this.flowPropertyValueChangeListeners != null) {
            for (FlowPropertyValueChangeListener flowPropertyValueChangeListener : this.flowPropertyValueChangeListeners) {
                if (isDynamic(flowPropertyValueChangeListener)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isDynamic(Object object) {
        if (object instanceof PossibleDynamic) {
            return ((PossibleDynamic) object).isDynamic();
        } else {
            return false;
        }
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || !(o instanceof FlowPropertyDefinitionImpl)) {
            return false;
        } else if (o == this) {
            return true;
        }
        FlowPropertyDefinitionImpl flowPropertyDefinition = (FlowPropertyDefinitionImpl) o;
        EqualsBuilder equalsBuilder = new EqualsBuilder().append(this.alternates, flowPropertyDefinition.alternates)
                .append(this.autoCreate, flowPropertyDefinition.autoCreate)
                .append(this.dataClassDefinition, flowPropertyDefinition.dataClassDefinition)
                .append(this.getExternalPropertyAccessRestriction(),
                        flowPropertyDefinition.getExternalPropertyAccessRestriction())
                .append(this.flowPropertyValueProvider, flowPropertyDefinition.flowPropertyValueProvider)
                .append(this.flowPropertyValuePersister, flowPropertyDefinition.getFlowPropertyValuePersister())
                .append(this.flowPropertyValueChangeListeners,
                        flowPropertyDefinition.getFlowPropertyValueChangeListeners())
                .append(this.initial, flowPropertyDefinition.initial).append(this.name, flowPropertyDefinition.name)
                // use getter so that defaults can be calculated.
                .append(this.getPropertyRequired(), flowPropertyDefinition.getPropertyRequired())
                .append(this.getPropertyUsage(), flowPropertyDefinition.getPropertyUsage())
                .append(this.getPropertyScope(), flowPropertyDefinition.getPropertyScope())
                .append(this.propertiesDependentOn, flowPropertyDefinition.getPropertiesDependentOn())
                .append(this.saveBack, flowPropertyDefinition.saveBack);
        return equalsBuilder.isEquals();
    }

    /**
     * @see org.amplafi.flow.FlowPropertyDefinition#isNamed(java.lang.String)
     */
    @Override
    public boolean isNamed(String possiblePropertyName) {
        if (isBlank(possiblePropertyName)) {
            return false;
        } else {
            return getName().equals(possiblePropertyName)
                    || (this.getAlternates() != null && this.getAlternates().contains(possiblePropertyName));
        }
    }

    @Override
    public boolean isNamed(Class<?> byClassName) {
        return this.isNamed(FlowPropertyDefinitionBuilder.toPropertyName(byClassName));
    }

    @Override
    public boolean isApplicable(FlowPropertyExpectation flowPropertyExpectation) {
        return this.isNamed(flowPropertyExpectation.getName());
    }

    @Override
    public String getNamespaceKey(FlowState flowState, FlowPropertyProvider flowPropertyProvider) {
        if (flowPropertyProvider == null) {
            return this.getNamespaceKey(flowState);
        } else if (flowPropertyProvider instanceof FlowActivity) {
            return this.getNamespaceKey((FlowActivity) flowPropertyProvider);
        } else if (flowPropertyProvider instanceof FlowState) {
            FlowImplementor flow = ((FlowState) flowPropertyProvider).getFlow();
            return this.getNamespaceKey(flow);
        } else {
            return this.getNamespaceKey((FlowImplementor) flowPropertyProvider);
        }
    }

    private String getNamespaceKey(FlowActivity flowActivity) {
        switch (this.getPropertyScope()) {
        case activityLocal:
            // TODO should really be ? but what about morphing??
            //            list.add(flowActivity.getFullActivityName());
            return flowActivity.getFullActivityInstanceNamespace();
        case flowLocal:
        case requestFlowLocal:
            return getNamespaceKey((FlowImplementor) flowActivity.getFlow());
        case global:
            return null;
        default:
            throw new IllegalStateException(flowActivity.getFlowPropertyProviderFullName() + ":" + this + ":"
                    + this.getPropertyScope() + ": don't know namespace.");
        }
    }

    private String getNamespaceKey(FlowImplementor flow) {
        switch (this.getPropertyScope()) {
        case flowLocal:
        case requestFlowLocal:
            if (flow.getFlowState() != null) {
                return getNamespaceKey(flow.getFlowState());
            } else {
                return flow.getFlowPropertyProviderName();
            }
        case global:
            return null;
        default:
            throw new IllegalStateException(flow.getFlowPropertyProviderFullName() + ":" + this + ":"
                    + this.getPropertyScope() + ": don't know namespace.");
        }
    }

    private String getNamespaceKey(FlowState flowState) {
        switch (this.getPropertyScope()) {
        case activityLocal:
            throw new IllegalStateException(
                    this + ":" + this.getPropertyScope() + " cannot be used when supplying only a FlowState");
        case flowLocal:
        case requestFlowLocal:
            return flowState.getLookupKey();
        case global:
            return null;
        default:
            throw new IllegalStateException(this.getPropertyScope() + ": don't know namespace.");
        }
    }

    @Override
    public List<String> getNamespaceKeySearchList(FlowState flowState, FlowPropertyProvider flowPropertyProvider,
            boolean forceAll) {
        if (flowPropertyProvider == null) {
            return this.getNamespaceKeySearchList(flowState, forceAll);
        } else if (flowPropertyProvider instanceof FlowActivity) {
            return this.getNamespaceKeySearchList((FlowActivity) flowPropertyProvider, forceAll);
        } else {
            FlowImplementor flow;
            if (flowPropertyProvider instanceof FlowState) {
                flow = ((FlowState) flowPropertyProvider).getFlow();
            } else if (flowPropertyProvider instanceof FlowImplementor) {
                flow = (FlowImplementor) flowPropertyProvider;
            } else {
                throw new FlowConfigurationException(flowPropertyProvider,
                        " is not a FlowState or FlowImplementor");
            }
            return this.getNamespaceKeySearchList(flow, forceAll);
        }
    }

    private List<String> getNamespaceKeySearchList(FlowImplementor flow, boolean forceAll) {
        //TODO have hierarchy of namespaces
        List<String> list = new ArrayList<String>();
        list.add(getNamespaceKey(flow));
        if (getPropertyUsage().isExternallySettable() || forceAll) {
            switch (this.getPropertyScope()) {
            case flowLocal:
            case requestFlowLocal:
                list.add(flow.getFlowPropertyProviderName());
                list.add(null);
            case global:
                break;
            default:
                throw new IllegalStateException(this.getPropertyScope() + ": don't know namespace.");
            }
        }
        return list;
    }

    /**
     * see notes on {@link #getNamespaceKeySearchList(FlowActivity, boolean)}
     * @param flowState
     * @param forceAll TODO
     * @param forExport TODO
     * @return list of namespace to search in order
     */
    private List<String> getNamespaceKeySearchList(FlowState flowState, boolean forceAll) {
        //TODO have hierarchy of namespaces
        List<String> list = new ArrayList<String>();
        list.add(getNamespaceKey(flowState));
        switch (this.getPropertyScope()) {
        case activityLocal:
            throw new IllegalStateException(
                    this + ":" + this.getPropertyScope() + " cannot be used when supplying only a FlowState");
        case flowLocal:
        case requestFlowLocal:
            list.add(flowState.getFlow().getFlowPropertyProviderName());
            if (getPropertyUsage().isExternallySettable() || forceAll) {
                list.add(null);
            }
        case global:
            break;
        default:
            throw new IllegalStateException(flowState.getLookupKey() + ":" + this + ":" + this.getPropertyScope()
                    + ": don't know namespace.");
        }
        return list;
    }

    /**
     * Ideally we have a hierarchy of namespaces that can be checked for values. This works o.k. for searching for a value.
     * But problem is once the value is found, should the old value be replaced in whatever name space? should the found value be moved to the more exact namespace?
     *
     * Example, if a activityLocal can look at namespaces={flowstate.lookupKey+activity.activityName, flow.typename+activity.activityname, etc.} should any found value
     * be moved to flowstate.lookupKey+activity.activityName?
     *
     * What about morphing which would change the flowtype and thus the flowtype+activity.activityName namespace value. Morphing would result in loosing activity local values.
     *
     * right now flow morph does loose flowLocal values.
     *
     * Maybe when a flow is created the name space gets set for all flowLocal and activityLocal based on the flowstate lookup key?
     *
     * More thought definitely needed.
     *
     * Seems like the namespace list should vary depending on if we are initializing a flow from external parameters. An external setting would not like
     * to have to worry about using a flowState's lookupKey to set the namespace. Would prefer to just use the key with no namespace, or maybe a more generic
     * namespace like FlowTypeName or ActivityName.
     * @param flowActivity
     * @param forceAll TODO
     * @return list of namespaces to search in order
     */
    private List<String> getNamespaceKeySearchList(FlowActivity flowActivity, boolean forceAll) {
        //TODO have hierarchy of namespaces
        List<String> list = new ArrayList<String>();
        list.add(getNamespaceKey(flowActivity));
        if (getPropertyUsage().isExternallySettable() || forceAll) {
            switch (this.getPropertyScope()) {
            case activityLocal:
                // TODO should really be ? but what about morphing??
                list.add(flowActivity.getFlowPropertyProviderFullName());
                list.add(flowActivity.getFlowPropertyProviderName());
            case flowLocal:
            case requestFlowLocal:
                list.add(flowActivity.getFlow().getFlowPropertyProviderName());
                list.add(null);
            case global:
                break;
            default:
                throw new IllegalStateException(this.getPropertyScope() + ": don't know namespace.");
            }
        }
        return list;
    }

    @Override
    public FlowPropertyDefinitionImpl merge(FlowPropertyExpectation flowPropertyExpectation) {
        throw new UnsupportedOperationException();
    }

    public FlowPropertyDefinitionImpl merge(Iterable<FlowPropertyExpectation> flowPropertyExpectations) {
        FlowPropertyDefinitionImpl result = this;
        for (FlowPropertyExpectation flowPropertyExpectation : NotNullIterator
                .<FlowPropertyExpectation>newNotNullIterator(flowPropertyExpectations)) {
            result = result.merge(flowPropertyExpectation);
        }
        return result;
    }
}