// properties can only be set by constructor, before initialising source features
  // (for joining nested mappings)
  private void setPropertyNames(Collection<PropertyName> propertyNames) {
    selectedProperties = new HashMap<AttributeMapping, List<PropertyName>>();

    if (propertyNames == null) {
      selectedMapping = mapping.getAttributeMappings();
    } else {
      final AttributeDescriptor targetDescriptor = mapping.getTargetFeature();
      selectedMapping = new ArrayList<AttributeMapping>();

      for (AttributeMapping attMapping : mapping.getAttributeMappings()) {
        final StepList targetSteps = attMapping.getTargetXPath();
        boolean alreadyAdded = false;

        if (includeMandatory) {
          PropertyName targetProp = namespaceAwareFilterFactory.property(targetSteps.toString());
          Object descr = targetProp.evaluate(targetDescriptor.getType());
          if (descr instanceof PropertyDescriptor) {
            if (((PropertyDescriptor) descr).getMinOccurs() >= 1) {
              selectedMapping.add(attMapping);
              selectedProperties.put(attMapping, new ArrayList<PropertyName>());
              alreadyAdded = true;
            }
          }
        }

        for (PropertyName requestedProperty : propertyNames) {
          StepList requestedPropertySteps;
          if (requestedProperty.getNamespaceContext() == null) {
            requestedPropertySteps =
                XPath.steps(targetDescriptor, requestedProperty.getPropertyName(), namespaces);
          } else {
            requestedPropertySteps =
                XPath.steps(
                    targetDescriptor,
                    requestedProperty.getPropertyName(),
                    requestedProperty.getNamespaceContext());
          }
          if (requestedPropertySteps == null
              ? AppSchemaDataAccess.matchProperty(requestedProperty.getPropertyName(), targetSteps)
              : AppSchemaDataAccess.matchProperty(requestedPropertySteps, targetSteps)) {
            if (!alreadyAdded) {
              selectedMapping.add(attMapping);
              selectedProperties.put(attMapping, new ArrayList<PropertyName>());
              alreadyAdded = true;
            }
            if (requestedPropertySteps != null
                && requestedPropertySteps.size() > targetSteps.size()) {
              List<PropertyName> pnList = selectedProperties.get(attMapping);
              StepList subProperty =
                  requestedPropertySteps.subList(targetSteps.size(), requestedPropertySteps.size());
              pnList.add(
                  filterFac.property(
                      subProperty.toString(), requestedProperty.getNamespaceContext()));
            }
          }
        }
      }
    }
  }
  public static IMappingFeatureIterator getInstance(
      AppSchemaDataAccess store, FeatureTypeMapping mapping, Query query, Filter unrolledFilter)
      throws IOException {

    if (mapping instanceof XmlFeatureTypeMapping) {
      return new XmlMappingFeatureIterator(store, mapping, query);
    }

    if (AppSchemaDataAccessConfigurator.isJoining()) {
      if (!(query instanceof JoiningQuery)) {
        query = new JoiningQuery(query);
      }
      FeatureSource mappedSource = mapping.getSource();
      FilterCapabilities capabilities = getFilterCapabilities(mappedSource);

      IMappingFeatureIterator iterator;
      if (unrolledFilter != null) {
        query.setFilter(Filter.INCLUDE);
        Query unrolledQuery = store.unrollQuery(query, mapping);
        unrolledQuery.setFilter(unrolledFilter);
        if (isSimpleType(mapping)) {
          iterator = new MappingAttributeIterator(store, mapping, query, false, unrolledQuery);
        } else {
          iterator =
              new DataAccessMappingFeatureIterator(store, mapping, query, false, unrolledQuery);
        }

      } else {
        Filter filter = query.getFilter();
        ComplexFilterSplitter splitter = new ComplexFilterSplitter(capabilities, mapping);
        filter.accept(splitter, null);

        query.setFilter(splitter.getFilterPre());
        filter = splitter.getFilterPost();
        int maxFeatures = Query.DEFAULT_MAX;
        if (filter != null && filter != Filter.INCLUDE) {
          maxFeatures = query.getMaxFeatures();
          query.setMaxFeatures(Query.DEFAULT_MAX);
        }
        iterator = new DataAccessMappingFeatureIterator(store, mapping, query, false);
        if (filter != null && filter != Filter.INCLUDE) {
          iterator = new PostFilteringMappingFeatureIterator(iterator, filter, maxFeatures);
        }
      }
      return iterator;
    } else {
      if (query.getFilter() != null) {
        Query unrolledQuery = store.unrollQuery(query, mapping);
        Filter filter = unrolledQuery.getFilter();
        CheckIfNestedFilterVisitor visitor = new CheckIfNestedFilterVisitor();
        filter.accept(visitor, null);
        if (visitor.hasNestedAttributes) {
          FeatureSource mappedSource = mapping.getSource();
          if (mappedSource instanceof JDBCFeatureSource
              || mappedSource instanceof JDBCFeatureStore) {
            FilterCapabilities capabilities = getFilterCapabilities(mappedSource);
            ComplexFilterSplitter splitter = new ComplexFilterSplitter(capabilities, mapping);
            filter.accept(splitter, null);
            query.setFilter(splitter.getFilterPre());
            unrolledQuery.setFilter(splitter.getFilterPre());
            filter = splitter.getFilterPost();
          } else {
            // VT:no Filtering capbilities cause source may not be of jdbc type
            // therefore we continue;
            // has nested attribute in the filter expression
            unrolledQuery.setFilter(Filter.INCLUDE);
          }
          return new FilteringMappingFeatureIterator(store, mapping, query, unrolledQuery, filter);
        } else if (!filter.equals(Filter.INCLUDE)
            && !filter.equals(Filter.EXCLUDE)
            && !(filter instanceof FidFilterImpl)) {
          // normal filters
          if (isSimpleType(mapping)) {
            return new MappingAttributeIterator(store, mapping, query, true, unrolledQuery);
          } else {
            return new DataAccessMappingFeatureIterator(store, mapping, query, true, unrolledQuery);
          }
        }
      }

      return new DataAccessMappingFeatureIterator(store, mapping, query, false);
    }
  }
 /**
  * Return a query appropriate to its underlying feature source.
  *
  * @param query the original query against the output schema
  * @return a query appropriate to be executed over the underlying feature source.
  */
 protected Query getUnrolledQuery(Query query) {
   return store.unrollQuery(query, mapping);
 }