private String[] parseDerefParts(Element fieldEl, String valueExpr) throws IndexerConfException {
    //
    // Split, normalize, validate the input
    //
    String[] derefParts = valueExpr.split(Pattern.quote("=>"));
    for (int i = 0; i < derefParts.length; i++) {
      String trimmed = derefParts[i].trim();
      if (trimmed.length() == 0) {
        throw new IndexerConfException(
            "Invalid dereference expression '"
                + valueExpr
                + "' at "
                + LocationAttributes.getLocationString(fieldEl));
      }
      derefParts[i] = trimmed;
    }

    if (derefParts.length < 2) {
      throw new IndexerConfException(
          "Invalid dereference expression '"
              + valueExpr
              + "' at "
              + LocationAttributes.getLocationString(fieldEl));
    }
    return derefParts;
  }
  private Value buildValue(Element fieldEl) throws Exception {
    String valueExpr = DocumentHelper.getAttribute(fieldEl, "value", true);

    Value value;

    boolean extractContent = DocumentHelper.getBooleanAttribute(fieldEl, "extractContent", false);

    String formatter = DocumentHelper.getAttribute(fieldEl, "formatter", false);
    if (formatter != null && !conf.getFormatters().hasFormatter(formatter)) {
      throw new IndexerConfException(
          "Formatter does not exist: "
              + formatter
              + " at "
              + LocationAttributes.getLocationString(fieldEl));
    }

    //
    // An index field can basically map to two kinds of values:
    //   * plain field values
    //   * dereference expressions (following links to some other record and then taking a field
    // value from it)
    //

    // A dereference expression is specified as "somelink=>somelink=>somefield"

    if (valueExpr.contains("=>")) {
      //
      // A dereference field
      //
      value = buildDerefValue(fieldEl, valueExpr, extractContent, formatter);
    } else {
      //
      // A plain field
      //
      value = new FieldValue(getFieldType(valueExpr, fieldEl), extractContent, formatter);
    }

    if (extractContent
        && !value
            .getTargetFieldType()
            .getValueType()
            .getDeepestValueType()
            .getBaseName()
            .equals("BLOB")) {
      throw new IndexerConfException(
          "extractContent is used for a non-blob value at "
              + LocationAttributes.getLocation(fieldEl));
    }

    return value;
  }
  private void processLessDimensionedVariantsDeref(
      Element fieldEl, String valueExpr, DerefValue deref, String derefPart)
      throws IndexerConfException {
    // The variant dimensions are specified in a syntax like "-var1,-var2,-var3"
    boolean validConfig = true;
    Set<String> dimensions = new HashSet<String>();
    for (String op : COMMA_SPLITTER.split(derefPart)) {
      if (op.length() > 1 && op.startsWith("-")) {
        String dimension = op.substring(1);
        dimensions.add(dimension);
      } else {
        validConfig = false;
        break;
      }
    }
    if (dimensions.size() == 0) validConfig = false;

    if (!validConfig) {
      throw new IndexerConfException(
          "Invalid specification of variants to follow: '"
              + derefPart
              + "', deref expression: '"
              + valueExpr
              + "' "
              + "at "
              + LocationAttributes.getLocation(fieldEl));
    }

    deref.addVariantFollow(dimensions);
  }
  private boolean processFieldDeref(
      Element fieldEl, String valueExpr, DerefValue deref, String derefPart)
      throws IndexerConfException, InterruptedException, RepositoryException {
    boolean lastFollowIsRecord;
    FieldType followField = getFieldType(derefPart, fieldEl);

    String type = followField.getValueType().getBaseName();
    if (type.equals("LIST")) {
      type = followField.getValueType().getNestedValueType().getBaseName();
    }

    if (type.equals("RECORD")) {
      deref.addRecordFieldFollow(followField);
      lastFollowIsRecord = true;
    } else if (type.equals("LINK")) {
      deref.addLinkFieldFollow(followField);
      lastFollowIsRecord = false;
    } else {
      throw new IndexerConfException(
          "Dereferencing is not possible on field of type "
              + followField.getValueType().getName()
              + ". Field: '"
              + derefPart
              + "', deref expression '"
              + valueExpr
              + "' at "
              + LocationAttributes.getLocation(fieldEl));
    }
    return lastFollowIsRecord;
  }
  private Map<String, String> parseVariantPropertiesPattern(Element caseEl) throws Exception {
    String variant = DocumentHelper.getAttribute(caseEl, "matchVariant", false);

    Map<String, String> varPropsPattern = new HashMap<String, String>();

    if (variant == null) return varPropsPattern;

    for (String prop : COMMA_SPLITTER.split(variant)) {
      int eqPos = prop.indexOf("=");
      if (eqPos != -1) {
        String propName = prop.substring(0, eqPos);
        String propValue = prop.substring(eqPos + 1);
        if (propName.equals("*")) {
          throw new IndexerConfException(
              String.format(
                  "Error in matchVariant attribute: the character '*' "
                      + "can only be used as wildcard, not as variant dimension name, attribute = %1$s, at: %2$s",
                  variant, LocationAttributes.getLocation(caseEl)));
        }
        varPropsPattern.put(propName, propValue);
      } else {
        varPropsPattern.put(prop, null);
      }
    }

    return varPropsPattern;
  }
  private void processMoreDimensionedVariantsDeref(
      Element fieldEl, String valueExpr, DerefValue deref, String derefPart)
      throws IndexerConfException {
    // The variant dimension is specified in a syntax like "+var1=boo,+var2"
    boolean validConfig = true;
    Map<String, String> dimensions = new HashMap<String, String>();
    for (String op : COMMA_SPLITTER.split(derefPart)) {
      if (op.length() > 1 && op.startsWith("+")) {
        final Iterator<String> keyAndValue = EQUAL_SIGN_SPLITTER.split(op).iterator();
        if (keyAndValue.hasNext()) {
          final String key = keyAndValue.next().substring(1); // ignore leading '+'
          if (keyAndValue.hasNext()) {
            // there is an equal sign -> key and value
            final String value = keyAndValue.next();
            dimensions.put(key, value);
          } else {
            // no equal sign -> only key without value
            dimensions.put(key, null);
          }
        } else {
          // nothing at all?
          validConfig = false;
          break;
        }
      } else {
        validConfig = false;
        break;
      }
    }
    if (dimensions.size() == 0) validConfig = false;

    if (!validConfig) {
      throw new IndexerConfException(
          "Invalid specification of variants to follow: '"
              + derefPart
              + "', deref expression: '"
              + valueExpr
              + "' "
              + "at "
              + LocationAttributes.getLocation(fieldEl));
    }

    deref.addForwardVariantFollow(dimensions);
  }
 private FieldType constructDerefFieldType(Element fieldEl, String valueExpr, String[] derefParts)
     throws IndexerConfException, InterruptedException, RepositoryException {
   //
   // Last element in the list should be a field
   //
   QName targetFieldName;
   try {
     targetFieldName = parseQName(derefParts[derefParts.length - 1], fieldEl);
   } catch (IndexerConfException e) {
     throw new IndexerConfException(
         "Dereference expression does not end on a valid field name. "
             + "Expression: '"
             + valueExpr
             + "' at "
             + LocationAttributes.getLocationString(fieldEl),
         e);
   }
   return getFieldType(targetFieldName);
 }
  private Value buildDerefValue(
      Element fieldEl, String valueExpr, boolean extractContent, String formatter)
      throws IndexerConfException, InterruptedException, RepositoryException {

    final String[] derefParts = parseDerefParts(fieldEl, valueExpr);
    final FieldType fieldType = constructDerefFieldType(fieldEl, valueExpr, derefParts);

    final DerefValue deref = new DerefValue(fieldType, extractContent, formatter);

    //
    // Run over all children except the last
    //
    boolean lastFollowIsRecord = false;
    for (int i = 0; i < derefParts.length - 1; i++) {
      String derefPart = derefParts[i];

      // A deref expression can navigate through 5 kinds of 'links':
      //  - a link stored in a link field (detected based on presence of a colon)
      //  - a nested record
      //  - a link to the master variant (if it's the literal string 'master')
      //  - a link to a less-dimensioned variant
      //  - a link to a more-dimensioned variant

      if (derefPart.contains(":")) { // It's a field name
        lastFollowIsRecord = processFieldDeref(fieldEl, valueExpr, deref, derefPart);
      } else if (derefPart.equals("master")) { // Link to master variant
        if (lastFollowIsRecord) {
          throw new IndexerConfException(
              "In dereferencing, master cannot follow on record-type field."
                  + " Deref expression: '"
                  + valueExpr
                  + "' at "
                  + LocationAttributes.getLocation(fieldEl));
        }
        lastFollowIsRecord = false;

        deref.addMasterFollow();
      } else if (derefPart.trim().startsWith("-")) { // Link to less dimensioned variant
        if (lastFollowIsRecord) {
          throw new IndexerConfException(
              "In dereferencing, variant cannot follow on record-type field."
                  + " Deref expression: '"
                  + valueExpr
                  + "' at "
                  + LocationAttributes.getLocation(fieldEl));
        }
        lastFollowIsRecord = false;

        processLessDimensionedVariantsDeref(fieldEl, valueExpr, deref, derefPart);
      } else if (derefPart.trim().startsWith("+")) { // Link to more dimensioned variant
        if (lastFollowIsRecord) {
          throw new IndexerConfException(
              "In dereferencing, variant cannot follow on record-type field."
                  + " Deref expression: '"
                  + valueExpr
                  + "' at "
                  + LocationAttributes.getLocation(fieldEl));
        }
        lastFollowIsRecord = false;

        processMoreDimensionedVariantsDeref(fieldEl, valueExpr, deref, derefPart);
      }
    }

    deref.init(typeManager);
    return deref;
  }
  private void buildDynamicFields() throws Exception {
    List<Element> fields = DYNAMIC_INDEX_FIELDS.get().evalAsNativeElementList(doc);
    for (Element fieldEl : fields) {
      String matchNamespaceAttr = DocumentHelper.getAttribute(fieldEl, "matchNamespace", false);
      String matchNameAttr = DocumentHelper.getAttribute(fieldEl, "matchName", false);
      String matchTypeAttr = DocumentHelper.getAttribute(fieldEl, "matchType", false);
      String matchScopeAttr = DocumentHelper.getAttribute(fieldEl, "matchScope", false);
      String nameAttr = DocumentHelper.getAttribute(fieldEl, "name", true);

      WildcardPattern matchNamespace = null;
      if (matchNamespaceAttr != null) {
        // If the matchNamespace attr does not contain a wildcard expression, and its value
        // happens to be an existing namespace prefix, than substitute the prefix for the full URI.
        if (!WildcardPattern.isWildcardExpression(matchNamespaceAttr)) {
          String uri = fieldEl.lookupNamespaceURI(matchNamespaceAttr);
          if (uri != null) matchNamespaceAttr = uri;
        }
        matchNamespace = new WildcardPattern(matchNamespaceAttr);
      }

      WildcardPattern matchName = null;
      if (matchNameAttr != null) {
        matchName = new WildcardPattern(matchNameAttr);
      }

      TypePattern matchTypes = null;
      if (matchTypeAttr != null) {
        matchTypes = new TypePattern(matchTypeAttr);
      }

      Set<Scope> matchScopes = null;
      if (matchScopeAttr != null) {
        matchScopes = EnumSet.noneOf(Scope.class);
        for (String scope : COMMA_SPLITTER.split(matchScopeAttr)) {
          matchScopes.add(Scope.valueOf(scope));
        }
        if (matchScopes.isEmpty()) {
          matchScopes = null;
        }
      }

      // Be gentle to users of Lily 1.0 and warn them about attributes that are not supported
      // anymore
      if (DocumentHelper.getAttribute(fieldEl, "matchMultiValue", false) != null) {
        log.warn(
            "The attribute matchMultiValue on dynamicField is not supported anymore, it will be ignored.");
      }
      if (DocumentHelper.getAttribute(fieldEl, "matchHierarchical", false) != null) {
        log.warn(
            "The attribute matchHierarchical on dynamicField is not supported anymore, it will be ignored.");
      }

      Set<String> variables = new HashSet<String>();
      variables.add("namespace");
      variables.add("name");
      variables.add("type");
      variables.add("baseType");
      variables.add("nestedType");
      variables.add("nestedBaseType");
      variables.add("deepestNestedBaseType");
      if (matchName != null && matchName.hasWildcard()) variables.add("nameMatch");
      if (matchNamespace != null && matchNamespace.hasWildcard()) variables.add("namespaceMatch");

      Set<String> booleanVariables = new HashSet<String>();
      booleanVariables.add("list");
      booleanVariables.add("multiValue");

      NameTemplate name = new NameTemplate(nameAttr, variables, booleanVariables);

      boolean extractContent = DocumentHelper.getBooleanAttribute(fieldEl, "extractContent", false);

      String formatter = DocumentHelper.getAttribute(fieldEl, "formatter", false);
      if (formatter != null && !conf.getFormatters().hasFormatter(formatter)) {
        throw new IndexerConfException(
            "Formatter does not exist: "
                + formatter
                + " at "
                + LocationAttributes.getLocationString(fieldEl));
      }

      boolean continue_ = DocumentHelper.getBooleanAttribute(fieldEl, "continue", false);

      DynamicIndexField field =
          new DynamicIndexField(
              matchNamespace,
              matchName,
              matchTypes,
              matchScopes,
              name,
              extractContent,
              continue_,
              formatter);

      conf.addDynamicIndexField(field);
    }
  }