public Object visit(AbstractDataFilter filter, Object rawContext) {
    VisitationContext context = (VisitationContext) rawContext;

    if (DataValueBean.isLargeValue(filter.getOperand())) {
      throw new InternalException(
          "Inlined data filter evaluation is not supported for big data values.");
    }

    final boolean isPrefetchHint = filter instanceof DataPrefetchHint;
    final boolean isFilterUsedInAndTerm = isAndTerm(context);
    final boolean isIsNullFilter = isIsNullFilter(filter);

    // join data_value table at most once for every dataID involved with the query, this
    // join will eventually be reused by successive DataFilters targeting the same
    // dataID (especially needed for ORed predicate to prevent combinatorial explosion)

    Pair joinKey = new Pair(Integer.valueOf(filter.getFilterMode()), new DataAttributeKey(filter));
    Join dvJoin = (Join) dataJoinMapping.get(joinKey);

    // collect qualifying data OIDs
    Map<Long, IData> dataMap =
        findAllDataRtOids(filter.getDataID(), context.getEvaluationContext().getModelManager());
    DataFilterExtension dataFilterExtension = SpiUtils.createDataFilterExtension(dataMap);

    final DataFilterExtensionContext dataFilterExtensionContext =
        context.getDataFilterExtensionContext();
    if (null == dvJoin) {
      // first use of this specific dataID, setup join
      // a dummy queryDescriptor needed here
      QueryDescriptor queryDescriptor =
          QueryDescriptor.from(ProcessInstanceBean.class)
              .select(
                  ProcessInstanceBean.FIELD__OID,
                  ProcessInstanceBean.FIELD__SCOPE_PROCESS_INSTANCE);

      JoinFactory joinFactory = new JoinFactory(context);
      dvJoin =
          dataFilterExtension.createDvJoin(
              queryDescriptor,
              filter,
              dataJoinMapping.size() + 1,
              dataFilterExtensionContext,
              isFilterUsedInAndTerm,
              joinFactory);

      // use INNER JOIN if both is valid
      // * filter is used in an AND term
      // * filter is NOT prefetch hint
      // otherwise use LEFT OUTER JOIN
      dvJoin.setRequired(isFilterUsedInAndTerm && !isPrefetchHint && !isIsNullFilter);

      if (dataFilterExtensionContext.useDistinct()) {
        context.useDistinct(dataFilterExtensionContext.useDistinct());
      }
      dataJoinMapping.put(joinKey, dvJoin);

      AndTerm andTerm = new AndTerm();
      dataFilterExtension.appendDataIdTerm(andTerm, dataMap, dvJoin, filter);
      if (andTerm.getParts().size() != 0) {
        dvJoin.where(andTerm);
      }
    } else {
      // if join already exists
      // * and filter is used in an AND term
      // * and filter is NOT prefetch hint
      // then force join to be an INNER JOIN
      // otherwise leave it as it is
      if (isFilterUsedInAndTerm && !isPrefetchHint && !isIsNullFilter) {
        dvJoin.setRequired(true);
      }
    }

    if (isPrefetchHint) {
      final List<FieldRef> selectExtension = context.getSelectExtension();
      selectExtension.addAll(dataFilterExtension.getPrefetchSelectExtension(dvJoin));

      return NOTHING;
    } else {
      return dataFilterExtension.createPredicateTerm(
          dvJoin, filter, dataMap, dataFilterExtensionContext);
    }
  }
    public Join createDataFilterJoins(
        int dataFilterMode, int idx, Class dvClass, FieldRef dvProcessInstanceField) {
      final Join dvJoin;

      final String pisAlias;
      final String pihAlias = "PR_PIH" + idx;
      final String dvAlias = "PR_" + dvProcessInstanceField.getType().getTableAlias() + idx;

      if (AbstractDataFilter.MODE_ALL_FROM_SCOPE == dataFilterMode) {
        if (isProcInstQuery) {
          dvJoin =
              new Join(dvClass, dvAlias) //
                  .on(
                      ProcessInstanceBean.FR__SCOPE_PROCESS_INSTANCE,
                      dvProcessInstanceField.fieldName);
        } else {
          Join glue = getGlueJoin();

          dvJoin =
              new Join(dvClass, dvAlias) //
                  .on(
                      glue.fieldRef(FIELD_GLUE_SCOPE_PROCESS_INSTANCE),
                      dvProcessInstanceField.fieldName);

          dvJoin.setDependency(glue);
        }
      } else if (AbstractDataFilter.MODE_SUBPROCESSES == dataFilterMode) {
        // TODO (peekaboo): Improve detection wether distinct is needed.
        // incSubProcModeCounter();
        context.useDistinct(true);

        FieldRef lhsFieldRef;
        if (isProcInstQuery) {
          lhsFieldRef = ProcessInstanceBean.FR__OID;
        } else if (isAiQueryUsingWorkItem) {
          lhsFieldRef = WorkItemBean.FR__PROCESS_INSTANCE;
        } else {
          lhsFieldRef = ActivityInstanceBean.FR__PROCESS_INSTANCE;
        }

        Join hierJoin =
            new Join(ProcessInstanceHierarchyBean.class, pihAlias) //
                .on(lhsFieldRef, ProcessInstanceHierarchyBean.FIELD__SUB_PROCESS_INSTANCE);

        dataJoinMapping.put(
            new Pair(Integer.valueOf(AbstractDataFilter.MODE_SUBPROCESSES), pihAlias), hierJoin);

        dvJoin =
            new Join(dvClass, dvAlias) //
                .on(
                    hierJoin.fieldRef(ProcessInstanceHierarchyBean.FIELD__PROCESS_INSTANCE),
                    dvProcessInstanceField.fieldName);

        dvJoin.setDependency(hierJoin);
      } else if (AbstractDataFilter.MODE_ALL_FROM_HIERARCHY == dataFilterMode) {
        // TODO (peekaboo): Improve detection wether distinct is needed.
        // incAllFromHierModeCounter();
        context.useDistinct(true);

        Join pisJoin;
        pisAlias = "PR_PIS" + idx;
        if (isProcInstQuery) {
          pisJoin =
              new Join(ProcessInstanceScopeBean.class, pisAlias) //
                  .on(
                      ProcessInstanceBean.FR__ROOT_PROCESS_INSTANCE,
                      ProcessInstanceScopeBean.FIELD__ROOT_PROCESS_INSTANCE);
        } else {
          Join glue = getGlueJoin();
          pisJoin =
              new Join(ProcessInstanceScopeBean.class, pisAlias) //
                  .on(
                      glue.fieldRef(FIELD_GLUE_ROOT_PROCESS_INSTANCE),
                      ProcessInstanceScopeBean.FIELD__ROOT_PROCESS_INSTANCE);

          pisJoin.setDependency(glue);
        }

        dataJoinMapping.put(
            new Pair(Integer.valueOf(AbstractDataFilter.MODE_ALL_FROM_HIERARCHY), pisAlias),
            pisJoin);

        dvJoin =
            new Join(dvClass, dvAlias) //
                .on(
                    pisJoin.fieldRef(ProcessInstanceScopeBean.FIELD__SCOPE_PROCESS_INSTANCE),
                    dvProcessInstanceField.fieldName);

        dvJoin.setDependency(pisJoin);
      } else {
        throw new InternalException(
            MessageFormat.format(
                "Invalid DataFilter mode: {0}.", new Object[] {Integer.valueOf(dataFilterMode)}));
      }

      return dvJoin;
    }